Commit b24de055 authored by Baptiste Jonglez's avatar Baptiste Jonglez

WIP passage à vue.js pour le frontend

parent b8b61063
# -*- coding: utf-8 -*-
from __future__ import unicode_literals, division, print_function
from math import log, tan, pi, radians
import matplotlib.pyplot as plt
import numpy as np
from django.core.management.base import BaseCommand, CommandError
from django.conf import settings
from panorama.models import Panorama, Reference
def bearing_diff(b1, b2):
"""In degrees"""
return (b1 - b2) % 360
def projection_factory(transform):
def projection(location, p1, p2):
"""For now, simply returns the scaling factors for x and y given two reference points"""
dx = (p2.x - p1.x) / (radians(bearing_diff(location.bearing(p2.reference_point), location.bearing(p1.reference_point))))
dy = (p2.y - p1.y) / (transform(location.elevation(p2.reference_point)) - transform(location.elevation(p1.reference_point)))
return (dx, dy)
return projection
equirectangular = projection_factory(lambda phi: radians(phi))
cylindrical = projection_factory(lambda phi: tan(radians(phi)))
mercator = projection_factory(lambda phi: log(tan(pi/4 + radians(phi)/2)))
projections = [('equirectangular', equirectangular),
('cylindrical', cylindrical),
('mercator', mercator)]
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument('pano_id', type=int)
def handle(self, *args, **options):
p = Panorama.objects.get(pk=options['pano_id'])
for proj_name, proj in projections:
self.stdout.write("\n{}".format(proj_name))
data = {'distx': [], 'disty': [], 'dx': [], 'dy': [], 'ref1': [], 'ref2': [], 'middlex': [], 'ref1.x': []}
for ref1 in p.panorama_references.order_by('x'):
for ref2 in p.panorama_references.order_by('x'):
if ref2 == ref1:
continue
if ref2.x >= ref1.x:
(dx, dy) = proj(p, ref1, ref2)
else:
(dx, dy) = proj(p, ref2, ref1)
distx = ref2.x - ref1.x
disty = ref2.y - ref1.y
middlex = (ref2.x + ref1.x) / 2
data['ref1'].append(ref1)
data['ref2'].append(ref2)
data['ref1.x'].append(ref1.x)
data['distx'].append(distx)
data['disty'].append(disty)
data['middlex'].append(middlex)
data['dx'].append(dx)
data['dy'].append(dy)
#self.stdout.write('{} - {}'.format(ref1.reference_point.name, ref2.reference_point.name))
#self.stdout.write('dx = {}'.format(dx))
#self.stdout.write('dy = {}'.format(dy))
# Detect outliers
for axis in ['dx', 'dy']:
median = np.median(data[axis])
self.stdout.write("\nMedian for {}: {}".format(axis, median))
for ref1, ref2, d in zip(data['ref1'], data['ref2'], data[axis]):
if ref2.pk < ref1.pk:
continue
if abs(d - median) / median >= 0.15:
self.stdout.write("Outlier: ({:5}, {:5}) - ({:5}, {:5}) → {}={}".format(ref1.x, ref1.y, ref2.x, ref2.y, axis, d))
mediandx = np.median(data['dx'])
mediandy = np.median(data['dy'])
if proj_name == 'equirectangular':
for xvar in ['distx', 'disty', 'middlex', 'ref1.x']:
fig, ax = plt.subplots()
ax.scatter(x=data[xvar], y=data['dx'], c=data['ref1.x'], alpha=0.5)
ax.hlines([mediandx], 0, 1, transform=ax.get_yaxis_transform(), colors='r')
ax.set_title('dx as a function of {}'.format(xvar))
plt.show()
for xvar in ['dx', 'distx', 'disty', 'middlex', 'ref1.x']:
fig, ax = plt.subplots()
ax.scatter(x=data[xvar], y=data['dy'], c=data['ref1.x'], alpha=0.5)
ax.hlines([mediandy], 0, 1, transform=ax.get_yaxis_transform(), colors='r')
ax.set_title('dy as a function of {}, for {} projection'.format(xvar, proj_name))
plt.show()
......@@ -233,10 +233,9 @@ class Panorama(ReferencePoint):
panorama."""
return [{"id": r.pk,
"name": r.reference_point.name,
# Adapt to js-based coordinates (x between 0 and 1, y
# between -0.5 and 0.5)
"x": r.x / r.panorama.image_width,
"y": (r.y / r.panorama.image_height) - 0.5,
"kind": r.reference_point.kind,
"x": r.x,
"y": r.y,
"cap": self.bearing(r.reference_point),
"elevation": self.elevation(r.reference_point)}
for r in self.panorama_references.all()]
......
......@@ -105,10 +105,16 @@ body {
overflow:hidden;
}
#mon-canvas {
background-color:#000;
margin:auto;
display:block;
#app {
height: 100%;
}
#pano {
background-color: #000;
margin: auto;
display: block;
height: 600px;
cursor: crosshair;
}
#info {
......@@ -268,6 +274,7 @@ fieldset#adding {
color:#FFF;
background-color:rgba(100,0,0,0.5);
}
#map {
height: 100%;
padding: -10px;
......
This diff is collapsed.
var { LMap, LTileLayer, LMarker } = Vue2Leaflet;
/* Wrapping CRS for 360° panorama */
/*
SimpleWrapCRS = L.extend({}, L.CRS.Simple, {
wrapLng: [0, this.image_width],
});
*/
new Vue({
el: '#app',
components: { LMap, LTileLayer, LMarker },
data: {
markers: [],
image_height: 0,
image_width: 0,
pano_id: null,
panoTileURL: '/{z}-{x}-{y}.jpg',
pano: null, /* Leaflet map */
panoTiles: null, /* Tiles of the panorama */
panoMinZoom: -8,
panoMaxZoom: 3,
panoZoom: 0,
panoCenter: [0, 0],
panoMaxBounds: null,
panoCRS: L.CRS.Simple,
panoOptions: {
zoomControl: true,
},
panoTilesURL: '/media/tiles/243/{z}-{x}-{y}.jpg',
panoTilesOpacity: 1.0,
panoTilesOptions: {
minZoom: -8,
maxZoom: 3,
maxNativeZoom: 0,
noWrap: true,
bounds: null,
}
},
mounted() {
this.image_width = 12972;
this.image_height = 3008;
this.panoZoom = -3;
this.panoCenter = [-this.image_height/2, this.image_width/2];
// Set bounds to avoid panning to infinity, but wide enough so that it's not too annoying
// TODO: change the bounds dynamically depending on the zoom level
this.panoMaxBounds = [[this.image_height, -0.3*this.image_width], [-2*this.image_height, 1.3*this.image_width]];
/* Avoid loading inexistant tiles (generating lots of 404) */
this.panoTilesOptions.bounds = [[0, 0], [-this.image_height, this.image_width]];
//this.loadData();
//this.initPano();
//this.initMinimap();
},
methods: {
initPano() {
this.pano = L.map('pano', {
zoomControl: true,
crs: SimpleWrapCRS,
center: [-this.image_height/2, this.image_width/2],
// Set bounds to avoid panning to infinity, but wide enough so that it's not too annoying
// TODO: change the bounds dynamically depending on the zoom level
maxBounds: [[this.image_height, -0.3*this.image_width], [-2*this.image_height, 1.3*this.image_width]],
//maxBounds: [[this.image_height, Number.NEGATIVE_INFINITY], [-2*this.image_height, Number.POSITIVE_INFINITY]],
zoom: this.defaultZoomPano,
minZoom: this.minZoomPano,
maxZoom: this.maxZoomPano,
});
this.panoTiles = L.TileLayer(this.PanoTileURL, {
//this.panoTiles = L.TileLayer('/debug/tile/{z}/{x}/{y}.jpg', {
minZoom: this.minZoomPano,
maxZoom: this.maxZoomPano,
maxNativeZoom: 0,
opacity: 1.0,
noWrap: true,
//noWrap: false, // For 360° panorama, but does not work as expected: https://github.com/Leaflet/Leaflet/issues/6292
bounds: [[0, 0], [-this.image_height, this.image_width]], // Avoid loading inexistant tiles (generating lots of 404)
});
this.pano.addLayer(this.panoTiles);
},
initMinimap() {
},
},
});
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
This diff is collapsed.
......@@ -86,32 +86,29 @@
var crosshairIcon = L.icon({
iconUrl: '{% static "panorama/img/marker-blue.png" %}',
iconSize: [22, 35],
iconAnchor: [11, 35]
iconAnchor: [11, 35],
});
var panoIcon = L.icon({
iconUrl: '{% static "panorama/img/marker-red.png" %}',
iconSize: [22, 35],
iconAnchor: [11, 35],
popupAnchor: [0,-50]
popupAnchor: [0, -25],
});
var poiIcons = {
subscriber: L.icon({
iconUrl: '{% static "panorama/img/marker-circle-green.png" %}',
iconSize: [20, 20],
iconAnchor: [10, 10],
popupAnchor: [0,-20]
}),
waiting: L.icon({
iconUrl: '{% static "panorama/img/marker-circle-orange.png" %}',
iconSize: [20, 20],
iconAnchor: [10, 10],
popupAnchor: [0,-20]
}),
other: L.icon({
iconUrl: '{% static "panorama/img/marker-circle-purple.png" %}',
iconSize: [20, 20],
iconAnchor: [10, 10],
popupAnchor: [0,-20]
}),
};
......@@ -250,13 +247,16 @@
// Add searching form
SearchPlace();
openPopup = function (e) { this.openPopup(); };
closePopup = function (e) { this.closePopup(); };
// Create markers for panoramas
{% for pano in panoramas %}
var marker = L.marker([{{ pano.latitude }}, {{ pano.longitude }}], {icon: panoIcon, riseOnHover: true});
marker.on("click",function(){document.location.href="{% url 'panorama:view_pano' pano.id %}"});
var popup = marker.bindPopup('{{ pano.name }}',{className : 'markerpopup', closeButton: false,});
popup.on('mouseover', marker.openPopup);
popup.on('mouseout', marker.closePopup);
var popup = marker.bindPopup('{{ pano.name }}',{className : 'markerpopup', closeButton: false});
marker.on('mouseover', openPopup);
marker.on('mouseout', closePopup);
markerClusters.addLayer( marker );
allMarkers.push([{{ pano.latitude }}, {{ pano.longitude }}]);
{% endfor %}
......@@ -268,8 +268,8 @@
{% for poi in poi_list %}
var poiMarker = L.marker([{{ poi.latitude }}, {{ poi.longitude }}], {icon: poiIcons['{{ poi.kind }}'], riseOnHover: true});
var poiPopup = poiMarker.bindPopup('{{ poi.name }}', {className : 'markerpopup', closeButton: false});
poiPopup.on('mouseover', poiMarker.openPopup);
poiPopup.on('mouseout', poiMarker.closePopup);
poiMarker.on('mouseover', openPopup);
poiMarker.on('mouseout', closePopup);
pointsOfInterest.addLayer(poiMarker);
{% endfor %}
// Add a legend to the map
......
......@@ -37,15 +37,6 @@
<script src="{% static "panorama/js/js.cookie.js" %}"></script>
<script src="{% static "panorama/js/pano.js" %}"></script>
<script>
{% for zoom_level, data in panorama.tiles_data.items %}
zooms[{{ zoom_level }}] = new tzoom({{ zoom_level }});
zooms[{{ zoom_level }}].ntiles.x = {{ data.ntiles_x }};
zooms[{{ zoom_level }}].ntiles.y = {{ data.ntiles_y }};
zooms[{{ zoom_level }}].tile.width = {{ data.tile_width }};
zooms[{{ zoom_level }}].tile.height = {{ data.tile_height }};
zooms[{{ zoom_level }}].last_tile.width = {{ data.last_tile_width }};
zooms[{{ zoom_level }}].last_tile.height = {{ data.last_tile_height }};
{% endfor %}
{% for id, refpoint in panorama.refpoints_data %}
point_list[{{ id }}] = new Array("{{ refpoint.name|escapejs }}", {{ refpoint.distance }}, "{{ refpoint.distance|distance|escapejs }}", {{ refpoint.cap }}, {{ refpoint.elevation }}, {{ refpoint.elevation_ground }}, "{{ refpoint.url }}", "/api/v1/refpoints/{{ refpoint.id }}/");
......@@ -61,46 +52,89 @@
<script type='text/javascript' src="{% static "panorama/js/jquery-3.0.0.min.js" %}"></script>
<script type='text/javascript' src="{% static "panorama/js/leaflet.markercluster/1.3.0/leaflet.markercluster.js" %}"></script>
<script>
var markerClusters = L.markerClusterGroup({
spiderfyOnMaxZoom: false,
showCoverageOnHover: false,
maxClusterRadius: 20,
disableClusteringAtZoom: 19
});
var panoIcon = L.icon({
iconUrl: '{% static "panorama/img/marker-red.png" %}',
iconSize: [22, 35],
iconAnchor: [11, 35],
popupAnchor: [0,-50]
});
/* Used both on the mini-map and on the panorama view */
openPopup = function (e) { this.openPopup(); };
closePopup = function (e) { this.closePopup(); };
var poiIcons = {
subscriber: L.icon({
iconUrl: '{% static "panorama/img/marker-circle-green.png" %}',
iconSize: [20, 20],
iconAnchor: [10, 10],
popupAnchor: [0,-20]
}),
waiting: L.icon({
iconUrl: '{% static "panorama/img/marker-circle-orange.png" %}',
iconSize: [20, 20],
iconAnchor: [10, 10],
popupAnchor: [0,-20]
}),
other: L.icon({
iconUrl: '{% static "panorama/img/marker-circle-purple.png" %}',
iconSize: [20, 20],
iconAnchor: [10, 10],
popupAnchor: [0,-20]
}),
};
</script>
<script>
/* New leaflet-based panorama view */
var SimpleWrapCRS = L.extend({}, L.CRS.Simple, {
wrapLng: [0, image_width],
});
panoLeaflet = L.map('pano', {
zoomControl: true,
crs: SimpleWrapCRS,
center: [-image_height/2, image_width/2],
// Set bounds to avoid panning to infinity, but wide enough so that it's not too annoying
// TODO: change the bounds dynamically depending on the zoom level
maxBounds: [[image_height, -0.3*image_width], [-2*image_height, 1.3*image_width]],
//maxBounds: [[image_height, Number.NEGATIVE_INFINITY], [-2*image_height, Number.POSITIVE_INFINITY]],
zoom: -4,
minZoom: -8,
maxZoom: 3,
});
var tileLayer = new L.TileLayer(img_prefix + '/{z}-{x}-{y}.jpg', {
//var tileLayer = new L.TileLayer('/debug/tile/{z}/{x}/{y}.jpg', {
minZoom: -8,
maxZoom: 3,
maxNativeZoom: 0,
opacity: 1.0,
noWrap: true,
//noWrap: false, // For 360° panorama, but does not work as expected: https://github.com/Leaflet/Leaflet/issues/6292
bounds: [[0, 0], [-image_height, image_width]], // Avoid loading inexistant tiles (generating lots of 404)
});
panoLeaflet.addLayer(tileLayer);
var references = L.layerGroup();
{% for ref in panorama.references_data %}
var marker = L.marker([-{{ ref.y }}, {{ ref.x }}], {icon: poiIcons['{{ ref.kind }}'], riseOnHover: true});
var poiPopup = marker.bindPopup('{{ ref.name }}', {className : 'markerpopup', closeButton: false});
marker.on('mouseover', openPopup);
marker.on('mouseout', closePopup);
references.addLayer(marker);
{% endfor %}
panoLeaflet.addLayer(references);
</script>
<script>
/* Mini-map */
var markerClusters = L.markerClusterGroup({
spiderfyOnMaxZoom: false,
showCoverageOnHover: false,
maxClusterRadius: 20,
disableClusteringAtZoom: 19
});
var panoIcon = L.icon({
iconUrl: '{% static "panorama/img/marker-red.png" %}',
iconSize: [22, 35],
iconAnchor: [11, 35],
popupAnchor: [0, -25]
});
{% for pano in panoramas %}
{% if panorama.name != pano.name %}
var marker = L.marker([{{ pano.latitude }}, {{ pano.longitude }}], {icon: panoIcon, riseOnHover: true});
marker.on("click",function(){document.location.href="{% url 'panorama:view_pano' pano.id %}"});
var popup = marker.bindPopup('{{ pano.name }}',{className : 'markerpopup', closeButton: false,});
popup.on('mouseover', marker.openPopup);
popup.on('mouseout', marker.closePopup);
var popup = marker.bindPopup('{{ pano.name }}',{className : 'markerpopup', closeButton: false});
marker.on('mouseover', openPopup);
marker.on('mouseout', closePopup);
markerClusters.addLayer( marker );
{% endif %}
{% endfor %}
......@@ -109,14 +143,16 @@
{% for poi in poi_list %}
var marker = L.marker([{{ poi.latitude }}, {{ poi.longitude }}], {icon: poiIcons['{{ poi.kind }}'], riseOnHover: true});
var poiPopup = marker.bindPopup('{{ poi.name }}', {className : 'markerpopup', closeButton: false});
poiPopup.on('mouseover', marker.openPopup);
poiPopup.on('mouseout', marker.closePopup);
marker.on('mouseover', openPopup);
marker.on('mouseout', closePopup);
pointsOfInterest.addLayer(marker);
{% endfor %}
load_map();
</script>
<script>
// need to be after the initialization of the leaflet variables.
window.onload = load_pano
//window.onload = load_pano
</script>
{% endlocalize %}
{% endblock js %}
......@@ -185,9 +221,8 @@ style="padding-left:0px"
{% block pano %}
<canvas id="mon-canvas">
Ce message indique que ce navigateur est vétuste car il ne supporte pas <samp>canvas</samp> (IE6, IE7, IE8, ...)
</canvas>
<div id="pano">
</div>
<p id="info"></p>
<p id="insert"><select id="sel_point" name="known_points">
{% for id, refpoint in panorama.refpoints_data %}
......
{% extends "panorama/base.html" %}
{% load staticfiles %}
{% load i18n %}
{% load l10n %}
{% block title %}{{ panorama.name }}{% endblock title %}
{% block css %}
<link rel="stylesheet" type="text/css" href="{% static "panorama/leaflet/v1.3.3/leaflet.css" %}">
<link rel="stylesheet" type="text/css" href="{% static "panorama/css/leaflet.markercluster/1.3.0/MarkerCluster.css" %}">
<link rel="stylesheet" type="text/css" href="{% static "panorama/css/leaflet.markercluster/1.3.0/MarkerCluster.Default.css" %}">
<link rel="stylesheet" type="text/css" href="{% static "panorama/css/markercluster.override.css" %}" />
{% endblock css %}
{% block js %}
{% localize off %}
<script src="{% static "panorama/js/hide_n_showForm.js" %}"></script>
<script type='text/javascript' src="{% static "panorama/vue/v2.5.17/vue.min.js" %}"></script>
<script type='text/javascript' src="{% static "panorama/leaflet/v1.3.3/leaflet.js" %}"></script>
<script type='text/javascript' src="{% static "panorama/js/leaflet.markercluster/1.3.0/leaflet.markercluster.js" %}"></script>
<script type='text/javascript' src="{% static "panorama/js/vue2-leaflet.min.js" %}"></script>
<script type='text/javascript' src="{% static "panorama/js/panovue.js" %}"></script>
<script>
</script>
{% endlocalize %}
{% endblock js %}
{% block top-menu-title %}
<a class="navbar-brand" href="{% url 'panorama:main' %}"><i class="fa fa-fw fa-home"></i></a>
<a class="navbar-brand" href="">{{panorama.name}}</a>
{% endblock %}
{% block top-menu-pano-items %}
{% localize off %}
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><i class="fa fa-question-circle"></i>  {% trans "Parameters" %}<b class="caret"></b></a>
<ul class="dropdown-menu">
<li>
<div>
<p>{% trans "latitude:" %} <em><span id="pos_lat">{{ panorama.latitude }}</span>°</em></p>
<p>{% trans "longitude:" %} <em><span id="pos_lon">{{ panorama.longitude }}</span>°</em></p>
<p>{% trans "ground altitude:" %} <em><span id="pos_ground_alt">{{ panorama.ground_altitude|floatformat:-2 }}</span> m</em></p>
<p>{% trans "height above ground:" %} <em><span id="pos_height">{{ panorama.height_above_ground|floatformat:-2 }}</span> m</em></p>
<p>{% trans "altitude:" %} <em><span id="pos_alt">{{ panorama.altitude|floatformat:-2 }}</span> m</em></p>
</div>
</li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><i class="fa fa-gears"></i>  {% trans "Controls" %}<b class="caret"></b></a>
<ul class="dropdown-menu">
<li>
<label>{% trans "Zoom:" %} <input type="range" min="0" max="2" value="2" id="zoom_ctrl"/></label>
<label>{% trans "Bearing:" %} <input type="number" min="0" max="360" step="10" value="0" autofocus="" id="angle_ctrl"/></label>
<label>{% trans "Elevation:" %} <input type="number" min="-90" max="90" step="1" value="0" autofocus="" id="elvtn_ctrl"/></label>
</li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><i class="fa fa-search"></i>  {% trans "Locate" %}<b class="caret"></b></a>
<ul class="dropdown-menu">
<li>
<label class="form_col" title="La latitude ϵ [-90°, 90°]. Ex: 12.55257">{% trans "Latitude:" %}
<input name="loca_latitude" type="number" min="-90" max="90" id="loca_latitude"/></label>
<label class="form_col" title="La longitude ϵ [-180°, 180°]. Ex: 144.14723">{% trans "Longitude:" %}
<input name="loca_longitude" type="number" min="-180" max="180" id="loca_longitude"/></label>
<label class="form_col" title="L'altitude positive Ex: 170">{% trans "Altitude:" %}
<input name="loca_altitude" type="number" min="-400" id="loca_altitude"/></label>
<div class="loca_buttons">
<input type="button" class="btn btn-info btn-sm" value="{% trans "Locate" %}" id="loca_button"/>
<input type="button" class="btn btn-danger btn-sm" value="{% trans "Delete" %}" id="loca_erase"/>
</div>
</li>
</ul>
</li>
{% endlocalize %}
{% endblock %}
{% block sidebar %}
{% endblock %}
{% block wrapper-style %}
style="padding-left:0px"
{% endblock %}
{% block content %}
{% endblock content %}
{% block pano %}
<div id="app" class="container">
<div id="pano">
<l-map ref="pano" :options="panoOptions" :crs="panoCRS" :center="panoCenter" :maxBounds="panoMaxBounds" :zoom="panoZoom" :minZoom="panoMinZoom" :maxZoom="panoMaxZoom">
<l-tile-layer :url="panoTilesURL" :options="panoTilesOptions" :opacity="panoTilesOpacity"></l-tile-layer>
<l-marker v-for="marker in markers" :lat-lng="marker.coord"></l-marker>
</l-map>
</div>
</div>
<div id="minimap"></div>
<div id="info"></div>
<div id="hideshowminimap"><a href="#" title="Hide/show map"><i id="expandmap" class="fa fa-compress" aria-hidden="true"></i></a></div>
{% endblock pano %}
......@@ -44,7 +44,7 @@ class PanoramaUpload(CelutzLoginMixin, CreateView):
@method_decorator(ensure_csrf_cookie, name='dispatch')
class PanoramaView(CelutzLoginMixin, DetailView):
model = Panorama
template_name = "panorama/view.html"
template_name = "panorama/vue.html"
context_object_name = "panorama"
def get_context_data(self, **kwargs):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment