summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorÉtienne Loks <etienne.loks@peacefrogs.net>2012-08-15 01:38:28 +0200
committerÉtienne Loks <etienne.loks@peacefrogs.net>2012-08-22 02:03:18 +0200
commit1feeb42a1f12811106764ef8203bfe1a10362fb8 (patch)
treeb275800b96119265c55cc159f3a7fe41516b7ff7
parentd63c56a5f8350c61117efec402b3ae67cc4c5a9b (diff)
downloadChimère-1feeb42a1f12811106764ef8203bfe1a10362fb8.tar.bz2
Chimère-1feeb42a1f12811106764ef8203bfe1a10362fb8.zip
First working version of routing.
* add an utilitary Routing class to manage different routing system * implement the routing with routino * add a view to manage routes * itinerary panel template * JS management of routing: * manage flag markers on the map * request the route when start and finish flag are set * display the route and the itinerary description * add of two flags images * itinerary panel CSS * french translation update
-rw-r--r--chimere/locale/fr/LC_MESSAGES/django.po42
-rw-r--r--chimere/route.py87
-rw-r--r--chimere/static/chimere/css/styles.css47
-rw-r--r--chimere/static/chimere/img/flag-finish.pngbin0 -> 1652 bytes
-rw-r--r--chimere/static/chimere/img/flag-start.pngbin0 -> 1165 bytes
-rw-r--r--chimere/static/chimere/img/images_licences7
-rw-r--r--chimere/static/chimere/js/jquery.chimere.js102
-rw-r--r--chimere/templates/chimere/blocks/map_menu.html8
-rw-r--r--chimere/templates/chimere/blocks/map_params.html1
-rw-r--r--chimere/templatetags/chimere_tags.py2
-rw-r--r--chimere/urls.py8
-rw-r--r--chimere/views.py21
-rw-r--r--example_project/settings.py7
13 files changed, 312 insertions, 20 deletions
diff --git a/chimere/locale/fr/LC_MESSAGES/django.po b/chimere/locale/fr/LC_MESSAGES/django.po
index 4462ff1..f9874fd 100644
--- a/chimere/locale/fr/LC_MESSAGES/django.po
+++ b/chimere/locale/fr/LC_MESSAGES/django.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 0.2\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2012-08-20 21:17+0200\n"
+"POT-Creation-Date: 2012-08-22 01:59+0200\n"
"PO-Revision-Date: 2010-03-20 20:00+0100\n"
"Last-Translator: Étienne Loks <etienne.loks@peacefrogs.net>\n"
"MIME-Version: 1.0\n"
@@ -630,19 +630,19 @@ msgstr ""
msgid "Bad param"
msgstr "Mauvais paramètre"
-#: views.py:229
+#: views.py:234
msgid "There are missing field(s) and/or errors in the submited form."
msgstr "Il y a des champs manquants ou des erreurs dans ce formulaire."
-#: views.py:312
+#: views.py:317
msgid "Bad file. Please check it with an external software."
msgstr "Fichier incohérent. Merci de le vérifier avec un logiciel externe."
-#: views.py:434
+#: views.py:439
msgid "Comments/request on the map"
msgstr "Commentaires/requètes sur la carte"
-#: views.py:437
+#: views.py:442
msgid ""
"Thank you for your contribution. It will be taken into account. If you have "
"left your email you may be contacted soon for more details."
@@ -651,15 +651,15 @@ msgstr ""
"laissé votre courriel vous serez peut-être contacté bientôt pour plus de "
"détails."
-#: views.py:441
+#: views.py:446
msgid "Temporary error. Renew your message later."
msgstr "Erreur temporaire. Réenvoyez votre message plus tard."
-#: views.py:572
+#: views.py:577
msgid "No category available in this area."
msgstr "Pas de catégorie disponible sur cette zone."
-#: views.py:679
+#: views.py:702
msgid "Incorrect choice in the list"
msgstr "Choix incorrect dans la liste"
@@ -913,6 +913,32 @@ msgstr "Ce site utilise Chimère"
msgid "Map"
msgstr "Carte"
+#: templates/chimere/blocks/map_menu.html:5
+msgctxt "routing"
+msgid "From"
+msgstr "En partir"
+
+#: templates/chimere/blocks/map_menu.html:6
+msgctxt "routing"
+msgid "To"
+msgstr "Y aller"
+
+#: templates/chimere/blocks/map_menu.html:8
+msgid "Zoom in"
+msgstr "Zoomer en avant"
+
+#: templates/chimere/blocks/map_menu.html:9
+msgid "Zoom out"
+msgstr "Zoomer en arrière"
+
+#: templates/chimere/blocks/map_menu.html:10
+msgid "Center the map here"
+msgstr "Centrer la carte ici"
+
+#: templates/chimere/blocks/map_menu.html:16
+msgid "Itinerary"
+msgstr "Itinéraire"
+
#: templates/chimere/blocks/map_params.html:6
msgid "Permalink"
msgstr "Lien permanent"
diff --git a/chimere/route.py b/chimere/route.py
new file mode 100644
index 0000000..efc6763
--- /dev/null
+++ b/chimere/route.py
@@ -0,0 +1,87 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Copyright (C) 2012 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet>
+#
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# See the file COPYING for details.
+
+"""
+Routing management
+"""
+
+import os, re, shutil, tempfile
+from BeautifulSoup import BeautifulSoup
+from subprocess import Popen, PIPE
+from django.contrib.gis.gdal import DataSource
+
+from django.conf import settings
+
+class Router:
+ def route(self, lon1, lat1, lon2, lat2, transport='foot'):
+ '''
+ Get a list of geojson polylines
+ '''
+ return []
+
+class RoutinoRouter(Router):
+ re_desc = [re.compile("<tr class='n'>"), re.compile("<tr class='s'>"),
+ re.compile("<tr class='t'>")]
+ def route(self, lon1, lat1, lon2, lat2, session_id='', transport='foot'):
+ '''
+ Get a list of geojson polylines and route description
+ '''
+ language = settings.LANGUAGE_CODE.split('-')[0]
+ args = [settings.CHIMERE_ROUTING_ENGINE['PATH'],
+ "--dir=%s" % settings.CHIMERE_ROUTING_ENGINE['DB_PATH'],
+ "--transport=%s" % transport,
+ "--language=%s" % language,
+ "--shortest",
+ "--output-html",
+ "--output-gpx-track",
+ "--lat1=%0.15f" % lat1,
+ "--lon1=%0.15f" % lon1,
+ "--lat2=%0.15f" % lat2,
+ "--lon2=%0.15f" % lon2
+ ]
+ tmp_dir = tempfile.mkdtemp(prefix='chimere_') + os.sep
+ p = Popen(args, stdout=PIPE, cwd=tmp_dir)
+ p.communicate()
+ ds = DataSource(tmp_dir + 'shortest-track.gpx')
+ if not ds:
+ return [], None
+ layer = ds[0]
+ trk_layer = None
+ for layer in ds:
+ if layer.name == 'tracks':
+ trk_layer = layer
+ break
+ multilines = trk_layer.get_geoms()
+ res = []
+ for multiline in multilines:
+ res += [geom.geojson for geom in multiline]
+ desc = ['<table>']
+ # only keeping interessant lines of the desc
+ for line in open(tmp_dir + 'shortest.html').readlines():
+ if [True for r in self.re_desc if r.match(line)]:
+ desc.append(BeautifulSoup(line).prettify())
+ desc.append('</table>')
+ desc = BeautifulSoup('\n'.join(desc)).prettify()
+ shutil.rmtree(tmp_dir)
+ return res, desc
+router = None
+if settings.CHIMERE_ROUTING_ENGINE['ENGINE'] == 'routino':
+ router = RoutinoRouter()
+
diff --git a/chimere/static/chimere/css/styles.css b/chimere/static/chimere/css/styles.css
index 66f2be5..61dcc4e 100644
--- a/chimere/static/chimere/css/styles.css
+++ b/chimere/static/chimere/css/styles.css
@@ -15,7 +15,8 @@ a, a:link, a:visited, legend,
h2, h3, th, .action li, .action li a,
.action li li a, #no-js-message,
-#footer a, #footer a:link, #footer a:visited, .ui-widget-header{
+#footer a, #footer a:link, #footer a:visited, .ui-widget-header,
+#chimere_itinerary td.l{
color:#fff;
}
@@ -29,7 +30,10 @@ h2, h3, th, .action li, .action li a,
body, h2, h3, th,
.ui-widget-header,
-.action li.selected, #no-js-message{
+.action li.selected, #no-js-message,
+#content .olControlLayerSwitcher .layersDiv,
+#content .olControlLayerSwitcher span,
+#chimere_itinerary td.l{
background-color:#449506;
}
@@ -38,7 +42,7 @@ body, h2, h3, th,
}
fieldset, .action li, #content,
-#map-footer, #panel, #areas,
+#map-footer, #panel, #chimere_itinerary_panel, #areas,
#welcome, #detail, .detail_footer a,
#content .olControlLayerSwitcher .layersDiv,
#content .olControlLayerSwitcher span,
@@ -63,7 +67,7 @@ div.warning,
#content,
.action li.selected,
#content .olControlLayerSwitcher .layersDiv,
-#panel, #map-footer,
+#panel, #map-footer, #chimere_itinerary_panel,
#utils-div{
border:1px solid #327e04;
}
@@ -79,7 +83,7 @@ div.warning,
opacity:0.9;
}
-#panel, #areas, #detail, #category_detail{
+#panel, #areas, #detail, #category_detail, #chimere_itinerary_panel{
opacity:0.8;
}
@@ -413,6 +417,33 @@ ul#share li{
padding-top:0;
}
+#chimere_itinerary_panel{
+ position:absolute;
+ z-index:5;
+ top:50px;
+ left:50px;
+ width:300px;
+ padding:0.5em;
+ padding-top:0;
+ display:none;
+}
+
+#chimere_itinerary{
+ height:250px;
+ overflow:auto;
+ padding:0.2em 1em;
+ font-size:0.9em;
+}
+
+#chimere_itinerary td.l{
+ padding:6px;
+}
+
+#chimere_itinerary span.j{
+ font-style:italic;
+}
+
+
#chimere_map_menu{
z-index:4;
display:none;
@@ -420,9 +451,9 @@ ul#share li{
padding:0.5em;
background-color:#fff;
border:1px solid #bbb;
- -webkit-border-radius: 4px;
- -moz-border-radius: 4px;
- border-radius: 4px;
+ -webkit-border-radius: 0 8px 8px 8px;
+ -moz-border-radius: 0 8px 8px 8px;
+ border-radius: 0 8px 8px 8px;
}
#chimere_map_menu ul, #chimere_map_menu li{
diff --git a/chimere/static/chimere/img/flag-finish.png b/chimere/static/chimere/img/flag-finish.png
new file mode 100644
index 0000000..04bfa1d
--- /dev/null
+++ b/chimere/static/chimere/img/flag-finish.png
Binary files differ
diff --git a/chimere/static/chimere/img/flag-start.png b/chimere/static/chimere/img/flag-start.png
new file mode 100644
index 0000000..c93f2a3
--- /dev/null
+++ b/chimere/static/chimere/img/flag-start.png
Binary files differ
diff --git a/chimere/static/chimere/img/images_licences b/chimere/static/chimere/img/images_licences
index 0e732fc..000fdf0 100644
--- a/chimere/static/chimere/img/images_licences
+++ b/chimere/static/chimere/img/images_licences
@@ -30,3 +30,10 @@ Url 2: http://commons.wikimedia.org/wiki/File:Internet-web-browser.svg
Author: Sbrools
Licence: Public domain
Url: https://commons.wikimedia.org/wiki/File:8thNote.svg
+
+* Flags image credit (flag-start.png, flag-finish.png)
+
+Author: FatCow Web Hosting
+Licence: Creative Commons Attribution 3.0 United States license
+Url: https://upload.wikimedia.org/wikipedia/commons/6/64/Farm-Fresh_flag_finish.png
+ https://upload.wikimedia.org/wikipedia/commons/c/cb/Farm-Fresh_flag_1.png
diff --git a/chimere/static/chimere/js/jquery.chimere.js b/chimere/static/chimere/js/jquery.chimere.js
index 1c2a973..3ffb1f9 100644
--- a/chimere/static/chimere/js/jquery.chimere.js
+++ b/chimere/static/chimere/js/jquery.chimere.js
@@ -226,11 +226,30 @@ OpenLayers.Layer.MapQuestOSM = OpenLayers.Class(OpenLayers.Layer.XYZ, {
settings.layerVectors, helpers.featureRouteCreated);
}
}
- $('#chimere_map_menu #map_menu_zoomin').bind("click",
- methods.zoomIn);
- $('#chimere_map_menu #map_menu_zoomout').bind("click",
- methods.zoomOut);
+ $('#map_menu_zoomin').bind("click", methods.zoomIn);
+ $('#map_menu_zoomout').bind("click", methods.zoomOut);
$('#map_menu_center').bind("click", methods.mapCenter);
+ if (settings.routing){
+ settings.routing_start = null;
+ settings.routing_end = null;
+ settings.icon_start = new OpenLayers.Icon(
+ STATIC_URL + "chimere/img/flag-start.png",
+ new OpenLayers.Size(32, 32),
+ new OpenLayers.Pixel(0, -32));
+ settings.icon_end = new OpenLayers.Icon(
+ STATIC_URL + "chimere/img/flag-finish.png",
+ new OpenLayers.Size(32, 32),
+ new OpenLayers.Pixel(0, -32));
+ $('#map_menu_from').bind("click", methods.routingFrom);
+ $('#map_menu_to').bind("click", methods.routingTo);
+ settings.layerRoute = new OpenLayers.Layer.Vector("Route Layer");
+ settings.map.addLayer(settings.layerRoute);
+ settings.layerRoute.setOpacity(0.8);
+ settings.layerRouteMarker = new OpenLayers.Layer.Markers(
+ 'Route markers');
+ settings.map.addLayer(settings.layerRouteMarker);
+ settings.layerRouteMarker.setOpacity(0.8);
+ }
}, // end of init
// zoom in from the map menu
@@ -494,6 +513,81 @@ OpenLayers.Layer.MapQuestOSM = OpenLayers.Class(OpenLayers.Layer.XYZ, {
settings.current_feature.geometry = linestring;
settings.layerVectors.addFeatures([settings.current_feature]);
},
+ // set the start point for routing
+ routingFrom: function(){
+ $('#chimere_map_menu').hide();
+ $('#chimere_itinerary_panel').hide();
+ settings.routing_start = new OpenLayers.Marker(
+ settings.current_position.clone(),
+ settings.icon_start);
+ settings.layerRouteMarker.addMarker(settings.routing_start);
+ if (settings.routing_end) methods.route();
+ },
+
+ // set the finish point for routing
+ routingTo: function(){
+ $('#chimere_map_menu').hide();
+ $('#chimere_itinerary_panel').hide();
+ settings.routing_end = new OpenLayers.Marker(
+ settings.current_position.clone(),
+ settings.icon_end);
+ settings.layerRouteMarker.addMarker(settings.routing_end);
+ if (settings.routing_start) methods.route();
+ },
+
+ // display a route
+ route: function(){
+ if (!settings.routing_start || !settings.routing_end){
+ return;
+ }
+ var start = settings.routing_start.lonlat.clone().transform(
+ settings.map.getProjectionObject(),
+ EPSG_DISPLAY_PROJECTION);
+ var end = settings.routing_end.lonlat.clone().transform(
+ settings.map.getProjectionObject(),
+ EPSG_DISPLAY_PROJECTION);
+ var uri = extra_url + "route/"
+ + start.lon + '_'
+ + start.lat + '_'
+ + end.lon + '_'
+ + end.lat;
+ $.ajax({url: uri,
+ dataType: "json",
+ success: function (data) {
+ settings.layerRoute.removeAllFeatures();
+ for (var i = 0; i < data.features.length; i++) {
+ methods.putRoute(data.features[i]);
+ }
+ $('#chimere_itinerary').html(
+ data.properties.description);
+ $('#chimere_itinerary_panel').show();
+ },
+ error: function (data) {
+ settings.layerRoute.removeAllFeatures();
+ }
+ });
+
+ },
+ /*
+ Put a route on the map
+ */
+ putRoute: function(polyline) {
+ var point_array = new Array();
+ for (i=0; i<polyline.coordinates.length; i++){
+ var point = new OpenLayers.Geometry.Point(polyline.coordinates[i][0],
+ polyline.coordinates[i][1]);
+ point_array.push(point);
+ }
+ var linestring = new OpenLayers.Geometry.LineString(point_array);
+ linestring.transform(EPSG_DISPLAY_PROJECTION, settings.map.getProjectionObject());
+ current_route = new OpenLayers.Feature.Vector();
+ var style = OpenLayers.Util.extend({},
+ OpenLayers.Feature.Vector.style['default']);
+ style.strokeWidth = 3;
+ current_route.style = style;
+ current_route.geometry = linestring;
+ settings.layerRoute.addFeatures([current_route]);
+ },
display_feature_detail: function (pk) {
/*
* update current detail panel with an AJAX request
diff --git a/chimere/templates/chimere/blocks/map_menu.html b/chimere/templates/chimere/blocks/map_menu.html
index 513d6ba..4c78a5b 100644
--- a/chimere/templates/chimere/blocks/map_menu.html
+++ b/chimere/templates/chimere/blocks/map_menu.html
@@ -10,3 +10,11 @@
<li id='map_menu_center'>{% trans "Center the map here" %}</li>
</ul>
</div>
+
+{% if routing %}
+<div id='chimere_itinerary_panel' class='draggable ui-widget ui-corner-all'>
+ <h2 class='ui-widget ui-state-default ui-corner-all ui-widget-header'>{% trans "Itinerary" %}</h2>
+ <div id='chimere_itinerary'>
+ </div>
+</div>
+{% endif%}
diff --git a/chimere/templates/chimere/blocks/map_params.html b/chimere/templates/chimere/blocks/map_params.html
index c8f97eb..1afd722 100644
--- a/chimere/templates/chimere/blocks/map_params.html
+++ b/chimere/templates/chimere/blocks/map_params.html
@@ -5,6 +5,7 @@
chimere_init_options["map_layers"] = [{{map_layers|safe|escape}}];
chimere_init_options['permalink_label'] = '{%trans "Permalink"%}';
chimere_init_options['permalink_element'] = document.getElementById('permalink');
+ chimere_init_options['routing'] = {{routing}};
{% if dynamic_categories %}chimere_init_options['dynamic_categories'] = true;{% endif %}
{% if default_area %}
chimere_init_options["default_area"] = new Array({{default_area.upper_left_corner.x}}, {{default_area.upper_left_corner.y}}, {{default_area.lower_right_corner.x}}, {{default_area.lower_right_corner.y}});
diff --git a/chimere/templatetags/chimere_tags.py b/chimere/templatetags/chimere_tags.py
index 70dabaa..5256518 100644
--- a/chimere/templatetags/chimere_tags.py
+++ b/chimere/templatetags/chimere_tags.py
@@ -123,6 +123,8 @@ def map_params(context):
context_data['icon_offset_y'] = settings.CHIMERE_ICON_OFFSET_Y
context_data['icon_width'] = settings.CHIMERE_ICON_WIDTH
context_data['icon_height'] = settings.CHIMERE_ICON_HEIGHT
+ context_data['routing'] = 'true' if settings.CHIMERE_ENABLE_ROUTING \
+ else 'none'
area_name = context['area_name'] if 'area_name' in context else 'area_name'
map_layers, default_area = get_map_layers(area_name)
context_data['map_layers'] = ", ".join(map_layers)
diff --git a/chimere/urls.py b/chimere/urls.py
index a232382..ad88e32 100644
--- a/chimere/urls.py
+++ b/chimere/urls.py
@@ -51,6 +51,14 @@ if settings.CHIMERE_FEEDS:
LatestPOIsByZoneID(), name='feeds-areaid'),
)
+if settings.CHIMERE_ENABLE_ROUTING:
+ urlpatterns += patterns('chimere.views',
+ url(r'^(?P<area_name>[a-zA-Z0-9_-]*/)?route/'\
+ r'(?P<lon1>[-]?[0-9]+[.]?[0-9]*)_(?P<lat1>[-]?[0-9]+[.]?[0-9]*)_'\
+ r'(?P<lon2>[-]?[0-9]+[.]?[0-9]*)_(?P<lat2>[-]?[0-9]+[.]?[0-9]*)$',
+ 'route', name="route"),
+ )
+
urlpatterns += patterns('chimere.views',
url(r'^charte/?$', 'charte', name="charte"),
url(r'^(?P<area_name>[a-zA-Z0-9_-]+/)?contact/?$', 'contactus', name="contact"),
diff --git a/chimere/views.py b/chimere/views.py
index f61e209..ad4b0ca 100644
--- a/chimere/views.py
+++ b/chimere/views.py
@@ -26,6 +26,7 @@ Views of the project
import datetime
from itertools import groupby
+import simplejson
from django.conf import settings
from django.core import serializers
@@ -49,6 +50,8 @@ from chimere.forms import MarkerForm, RouteForm, ContactForm, FileForm, \
FullFileForm, MultimediaFileFormSet, PictureFileFormSet, notifySubmission,\
notifyStaff, AreaForm
+from chimere.route import router
+
def get_base_uri(request):
base_uri = 'http://'
if 'HTTP_REFERER' in request.META:
@@ -635,6 +638,24 @@ def redirectFromTinyURN(request, area_name='', tiny_urn=''):
return redir
return HttpResponseRedirect(response_dct['extra_url'] + parameters)
+def route(request, area_name, lon1, lat1, lon2, lat2, transport='foot'):
+ '''
+ Get the JSON for a route
+ '''
+ try:
+ lon1, lat1 = float(lon1), float(lat1)
+ lon2, lat2 = float(lon2), float(lat2)
+ except ValueError:
+ return HttpResponse('no results')
+ jsons, desc = router.route(lon1, lat1, lon2, lat2, transport=transport,
+ session_id=request.session.session_key)
+ if not jsons:
+ return HttpResponse('no results')
+ jsonencoder = simplejson.JSONEncoder()
+ data = '{"properties":{"description":%s}, "type": "FeatureCollection",'\
+ '"features":[%s]}' % (jsonencoder.encode(desc), ",".join(jsons))
+ return HttpResponse(data)
+
def rss(request, area_name=''):
'''
Redirect to RSS subscription page
diff --git a/example_project/settings.py b/example_project/settings.py
index 448da80..5684386 100644
--- a/example_project/settings.py
+++ b/example_project/settings.py
@@ -78,6 +78,13 @@ CHIMERE_SHAPEFILE_ENCODING = 'ISO-8859-1'
# enable routing in Chimère
CHIMERE_ENABLE_ROUTING = False
+# available routing engine: 'routino'
+CHIMERE_ROUTING_ENGINE = {
+ 'ENGINE': 'routino',
+ 'PATH': '/usr/local/src/web/bin/router',
+ 'DB_PATH': '/var/local/routino/',
+}
+
ADMINS = (
# ('Your Name', 'your_email@domain.com'),
)