diff options
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 |
commit | 1feeb42a1f12811106764ef8203bfe1a10362fb8 (patch) | |
tree | b275800b96119265c55cc159f3a7fe41516b7ff7 | |
parent | d63c56a5f8350c61117efec402b3ae67cc4c5a9b (diff) | |
download | Chimè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.po | 42 | ||||
-rw-r--r-- | chimere/route.py | 87 | ||||
-rw-r--r-- | chimere/static/chimere/css/styles.css | 47 | ||||
-rw-r--r-- | chimere/static/chimere/img/flag-finish.png | bin | 0 -> 1652 bytes | |||
-rw-r--r-- | chimere/static/chimere/img/flag-start.png | bin | 0 -> 1165 bytes | |||
-rw-r--r-- | chimere/static/chimere/img/images_licences | 7 | ||||
-rw-r--r-- | chimere/static/chimere/js/jquery.chimere.js | 102 | ||||
-rw-r--r-- | chimere/templates/chimere/blocks/map_menu.html | 8 | ||||
-rw-r--r-- | chimere/templates/chimere/blocks/map_params.html | 1 | ||||
-rw-r--r-- | chimere/templatetags/chimere_tags.py | 2 | ||||
-rw-r--r-- | chimere/urls.py | 8 | ||||
-rw-r--r-- | chimere/views.py | 21 | ||||
-rw-r--r-- | example_project/settings.py | 7 |
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 Binary files differnew file mode 100644 index 0000000..04bfa1d --- /dev/null +++ b/chimere/static/chimere/img/flag-finish.png diff --git a/chimere/static/chimere/img/flag-start.png b/chimere/static/chimere/img/flag-start.png Binary files differnew file mode 100644 index 0000000..c93f2a3 --- /dev/null +++ b/chimere/static/chimere/img/flag-start.png 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'), ) |