Browse Source

Implement the view for point location

Baptiste Jonglez 9 years ago
parent
commit
b15d79a122

+ 20 - 0
panorama/forms.py

@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django import forms
+
+from .models import Point, ReferencePoint
+
+
+class SelectReferencePointForm(forms.Form):
+    """Form to select an existing reference point"""
+    q = ReferencePoint.objects.order_by("name")
+    reference_point = forms.ModelChoiceField(queryset=q)
+
+
+class CustomPointForm(forms.ModelForm):
+    """Form to use a custom point as input."""
+
+    class Meta:
+        model = Point
+        fields = ['latitude', 'longitude', 'altitude']

+ 4 - 0
panorama/templates/panorama/list.html

@@ -15,6 +15,10 @@
 
 <p><a href="{% url 'panorama:new' %}">Add new panorama</a></p>
 
+<h2>Locate a point</h2>
+
+<p><a href="{% url 'panorama:locate' %}">Locate a point</a> by listing all panoramas that can see this point</p>
+
 <h2>Add reference point</h2>
 
 <p><a href="{% url 'admin:app_list' 'panorama' %}">In the administration interface</a> for now.</p>

+ 46 - 0
panorama/templates/panorama/locate_point.html

@@ -0,0 +1,46 @@
+{% load panorama_url %}
+{% load distance_filter %}
+
+<h1>Locate a point</h1>
+
+{% if panoramas %}
+
+<h2>Result</h2>
+
+<p>Panoramas that see the point
+  <strong>{% if point_name %}{{ point_name }}{% else %}{{ point_lat }}°, {{ point_lon }}°{% endif %}</strong>:
+</p>
+
+<p>
+<ul>
+{% for panorama, distance, bearing, elevation in panoramas %}
+<li><a href="{% panorama_url panorama bearing elevation %}">{{ panorama.name }}</a> at {{ distance|distance }} (click to visualise)</li>
+{% endfor %}
+</ul>
+</p>
+
+{% endif %}
+
+<h2>How it works</h2>
+
+<p>
+  <ol>
+    <li>Select an existing point, or enter a custom point by its coordinates;</li>
+    <li>You obtain a list of panoramas that can see the point;</li>
+    <li>Click on a panorama to visualise exactly the point you asked for!</li>
+  </ol>
+</p>
+
+<h2>Locate!</h2>
+
+<form action="{% url 'panorama:locate_refpoint' %}" enctype="multipart/form-data" method="post">{% csrf_token %}
+  {{ refpoints_form.as_p }}
+  <input type="submit" value="Locate" />
+</form>
+
+<p><strong>OR</strong></p>
+
+<form action="{% url 'panorama:locate_custompoint' %}" enctype="multipart/form-data" method="post">{% csrf_token %}
+  {{ custom_point_form.as_p }}
+  <input type="submit" value="Locate" />
+</form>

+ 0 - 0
panorama/templatetags/__init__.py


+ 16 - 0
panorama/templatetags/distance_filter.py

@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals, division, print_function
+
+from django import template
+
+
+register = template.Library()
+
+@register.filter
+def distance(value):
+    """Humanize distance"""
+    value = int(value)
+    if value < 1000:
+        return "{} m".format(value)
+    else:
+        return "{:.3g} km".format(value/1000)

+ 12 - 0
panorama/templatetags/panorama_url.py

@@ -0,0 +1,12 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals, division, print_function
+
+from django import template
+
+
+register = template.Library()
+
+@register.simple_tag
+def panorama_url(panorama, bearing, elevation):
+    """Return an URL to the given panorama, with given bearing and elevation"""
+    return panorama.get_absolute_url(bearing, elevation)

+ 4 - 1
panorama/urls.py

@@ -3,11 +3,14 @@ from __future__ import unicode_literals
 
 from django.conf.urls import patterns, include, url
 
-from .views import PanoramaUpload, PanoramaView, PanoramaList, PanoramaGenTiles
+from .views import PanoramaUpload, PanoramaView, PanoramaList, PanoramaGenTiles, LocatePointView, LocateReferencePointView, LocateCustomPointView
 
 
 urlpatterns = patterns('',
     url(r'^$', PanoramaList.as_view(), name="list"),
+    url(r'^locate/$', LocatePointView.as_view(), name="locate"),
+    url(r'^locate/refpoint/$', LocateReferencePointView.as_view(), name="locate_refpoint"),
+    url(r'^locate/custompoint/$', LocateCustomPointView.as_view(), name="locate_custompoint"),
     url(r'^pano/new/$', PanoramaUpload.as_view(), name="new"),
     url(r'^pano/view/(?P<pk>\d+)/$', PanoramaView.as_view(), name="view_pano"),
     url(r'^pano/gen_tiles/(?P<pk>\d+)/$', PanoramaGenTiles.as_view(), name="gen_tiles"),

+ 58 - 2
panorama/views.py

@@ -5,9 +5,10 @@ from django.conf import settings
 from django.core.urlresolvers import reverse_lazy
 from django.http import HttpResponse, JsonResponse
 from django.shortcuts import render, get_object_or_404
-from django.views.generic import CreateView, DetailView, RedirectView, ListView
+from django.views.generic import CreateView, DetailView, RedirectView, ListView, TemplateView
 
-from .models import Panorama, ReferencePoint
+from .models import Point, Panorama, ReferencePoint
+from .forms import SelectReferencePointForm, CustomPointForm
 
 
 class PanoramaUpload(CreateView):
@@ -38,3 +39,58 @@ class PanoramaList(ListView):
     model = Panorama
     template_name = "panorama/list.html"
     context_object_name = "panoramas"
+
+
+class LocatePointView(TemplateView):
+    """View to choose a point to locate (either an existing reference point,
+    or from GPS coordinates)"""
+    template_name = 'panorama/locate_point.html'
+
+    def get_context_data(self, **kwargs):
+        context = super(LocatePointView, self).get_context_data(**kwargs)
+        context['refpoints_form'] = SelectReferencePointForm
+        context['custom_point_form'] = CustomPointForm
+        return context
+
+    def compute_interesting_panoramas(self, point):
+        """Compute all panoramas that see the given point, along with the distance
+        and direction from each panorama towards the point.  Returns a
+        list of (panorama, distance, bearing, elevation) triples.
+        """
+        if isinstance(point, ReferencePoint):
+            queryset = Panorama.objects.exclude(id=point.id)
+        else:
+            queryset = Panorama.objects
+        l = [(pano, pano.line_distance(point), pano.bearing(point), pano.elevation(point))
+             for pano in queryset.all() if pano.is_visible(point)]
+        # Sort by increasing distance
+        return sorted(l, key=lambda x: x[1])
+
+
+class LocateReferencePointView(LocatePointView):
+    """Subclass that handles locating a reference point"""
+
+    def post(self, request, *args, **kwargs):
+        context = self.get_context_data()
+        form = SelectReferencePointForm(request.POST)
+        context['refpoints_form'] = form
+        if form.is_valid():
+            point = form.cleaned_data['reference_point']
+            context['panoramas'] = self.compute_interesting_panoramas(point)
+            context['point_name'] = point.name
+        return super(LocateReferencePointView, self).render_to_response(context)
+
+
+class LocateCustomPointView(LocatePointView):
+    """Subclass that handles locating a custom point"""
+
+    def post(self, request, *args, **kwargs):
+        context = self.get_context_data()
+        form = CustomPointForm(request.POST)
+        context['custom_point_form'] = form
+        if form.is_valid():
+            point = Point(**form.cleaned_data)
+            context['panoramas'] = self.compute_interesting_panoramas(point)
+            context['point_lat'] = point.latitude
+            context['point_lon'] = point.longitude
+        return super(LocateCustomPointView, self).render_to_response(context)