├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── app.css ├── app.js ├── examples ├── load-geojson.html ├── load-waypoints.html ├── osm.html ├── route.geojson └── styling.html ├── images ├── promo.gif └── promo.png ├── index.html ├── libs └── leaflet │ ├── images │ ├── layers.png │ ├── marker-icon.png │ ├── marker-icon@2x.png │ └── marker-shadow.png │ ├── leaflet.css │ ├── leaflet.ie.css │ └── leaflet.js └── src ├── L.Routing.Draw.js ├── L.Routing.Edit.js ├── L.Routing.Storage.js ├── L.Routing.js └── utils ├── LineUtil.Snapping.js ├── Marker.Snapping.js └── Polyline.Snapping.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | ### 0.2.0 (Unreadlesed) 5 | 6 | **Features** 7 | 8 | * Add option for overriding draw shortcuts (#23) 9 | * Add option for disabling drawing marker (#18) 10 | * Add options for tooltips (waypoint, segment) 11 | * Disable shortcuts by setting `options.shortcut` to `false`. 12 | * Add support for loading GeoJSON without waypoints (#16). 13 | * Add option to #loadGeoJSON() to disable map fit bounds. 14 | * Add [OSM demo](http://turistforeningen.github.io/leaflet-routing/examples/osm.html) 15 | * …as well as countless updates to the readme and code readability. 16 | 17 | **Breaking changes** 18 | 19 | * Default shortcut key for draw enable is `d` 20 | * Default shortcut key for draw disable is `q` 21 | 22 | ### 0.1.1 March 11, 2014 23 | 24 | **Features** 25 | 26 | * Add changelog overview (#14) 27 | * Add Change map bounds when loading GeoJSON (#13) 28 | 29 | **Bugfixes** 30 | 31 | * Fix undefined evaluation when snapping layer is not avaiable (#15) 32 | * Fail gracefully when loading invalid GeoJSON (#12) 33 | 34 | ### 0.1.0 March 10, 2014 35 | 36 | * Implements `#loadGeoJSON()` method (#3) 37 | 38 | #### Backwards compability note 39 | 40 | * Format of `properties.waypoints` in `#toGeoJSON()` is changed according to #3. 41 | 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2015, Den Norske Turistforening (DNT), Hans Kristian Flaatten 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are 5 | permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this list of 8 | conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, this list 11 | of conditions and the following disclaimer in the documentation and/or other materials 12 | provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 15 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 16 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 17 | COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 20 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 21 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Leaflet.Routing 2 | =============== 3 | [![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/Turistforeningen/leaflet-routing?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | 5 | Leaflet.Routing is a routing controller for the popular Leaflet mapping 6 | framework. The module provides an intuitive interface for routing paths between 7 | waypoints using any user specified routing service. A demo using the OSM data 8 | can be found 9 | [here](http://turistforeningen.github.io/leaflet-routing/examples/osm.html). 10 | 11 | ![Prototype Routing using Leaflet](https://raw.github.com/Turistforeningen/leaflet-routing/gh-pages/images/promo.gif) 12 | 13 | ## Features 14 | 15 | * Route handling interface for Leaflet 16 | * Use your own routing backend, or OSM 17 | 18 | ## Usage 19 | 20 | ```javascript 21 | var routing = new L.Routing({ 22 | position: 'topright' 23 | ,routing: { 24 | router: myRouterFunction 25 | } 26 | ,tooltips: { 27 | waypoint: 'Waypoint. Drag to move; Click to remove.', 28 | segment: 'Drag to create a new waypoint' 29 | } 30 | ,styles: { // see http://leafletjs.com/reference.html#polyline-options 31 | trailer: {} // drawing line 32 | ,track: {} // calculated route result 33 | ,nodata: {} // line when no result (error) 34 | } 35 | ,snapping: { 36 | layers: [mySnappingLayer] 37 | ,sensitivity: 15 38 | ,vertexonly: false 39 | } 40 | ,shortcut: { 41 | draw: { 42 | enable: 68 // 'd' 43 | ,disable: 81 // 'q' 44 | } 45 | } 46 | }); 47 | map.addControl(routing); 48 | ``` 49 | 50 | ### Enable Drawing 51 | 52 | ```javascript 53 | routing.draw(true); 54 | ``` 55 | 56 | ### Enable Routing `NOT IMPLEMENTED` 57 | 58 | ```javascript 59 | routing.routing(true); 60 | ``` 61 | 62 | ### Enable Snapping `NOT IMPLEMETED` 63 | 64 | ```javascript 65 | routing.snapping(true); 66 | ``` 67 | 68 | ### Recalculate the complete route by routing each segment 69 | 70 | ```javascript 71 | routing.rerouteAllSegments(callback); 72 | ``` 73 | 74 | ### Get first waypoint 75 | 76 | ```javascript 77 | var first = routing.getFirst(); 78 | ``` 79 | 80 | ### Get last waypoint 81 | 82 | ```javascript 83 | var last = routing.getLast(); 84 | ``` 85 | 86 | ### Get all waypoints 87 | 88 | ```javascript 89 | var waypointsArray = routing.getWaypoints(); 90 | ``` 91 | 92 | ### Routing to Polyline 93 | 94 | ```javascript 95 | var polyline = routing.toPolyline(); 96 | ``` 97 | 98 | ### To GeoJSON 99 | 100 | ```javascript 101 | var geoJSON3D = routing.toGeoJSON(); 102 | var geoJSON2D = routing.toGeoJSON(false); 103 | ``` 104 | 105 | ### Load GeoJSON 106 | 107 | Load GeoJSON with and without `properties.waypoints`. 108 | 109 | #### Options 110 | 111 | * `number` waypointDistance - distance between inserted waypoints for GeoJSON without waypoints. 112 | * `boolean` fitBounds - fit map arround loaded GeoJSON. 113 | 114 | ```javascript 115 | routing.loadGeoJSON(geojson, [options], function(err) { 116 | if (err) { 117 | console.log(err); 118 | } else { 119 | console.log('Finished loading GeoJSON'); 120 | } 121 | }); 122 | ``` 123 | 124 | ## Events 125 | 126 | All events form Leaflet.Routing is prefixed with `routing:`. 127 | 128 | ### Usage 129 | 130 | ```javascript 131 | routing.on('routing:someEvent', function() { 132 | console.log('routing:someEvent triggered'); 133 | }); 134 | ``` 135 | 136 | ### L.Routing Events 137 | 138 | | Event name | Description | 139 | |------------|-------------| 140 | | `routing:draw-start` | Fired when drawing mode is started | 141 | | `routing:draw-new` | Fired when drawing mode is started for a new route | 142 | | `routing:draw-continue` | Fired when drawing mode is started for an existing route | 143 | | `routing:draw-stop` | Fired when drawing mode ends | 144 | | `routing:edit-start` | Fired when editing mode starts | 145 | | `routing:edit-end` | Fired when editing mode ends | 146 | 147 | ### Waypoint Events 148 | 149 | | Event name | Description | 150 | |------------|-------------| 151 | | `routing:routeWaypointStart` | Fired when a new or existing waypoint is created or moved | 152 | | `routing:routeWaypointEnd` | Fired when routing is finished for new or moved waypoint | 153 | 154 | ### Segment Events 155 | 156 | | Event name | Description | 157 | |------------|-------------| 158 | | `routing:rerouteAllSegmentsStart` | Fired when rerouting of all segments starts | 159 | | `routing:rerouteAllSegmentsEnd` | Fired when rerouting of all segments completes | 160 | 161 | ## Copyright 162 | 163 | Copyright (c) 2014, Den Norske Turistforening 164 | 165 | All rights reserved. 166 | 167 | Redistribution and use in source and binary forms, with or without modification, are permitted 168 | provided that the following conditions are met: 169 | 170 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions 171 | and the following disclaimer. 172 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions 173 | and the following disclaimer in the documentation and/or other materials provided with the 174 | distribution. 175 | 176 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 177 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 178 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 179 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 180 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 181 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 182 | IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 183 | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 184 | 185 | -------------------------------------------------------------------------------- /app.css: -------------------------------------------------------------------------------- 1 | #map { 2 | position: absolute; 3 | top: 0; 4 | left: 0; 5 | width: 100%; 6 | height: 100%; 7 | } 8 | 9 | div.line-mouse-marker { 10 | background-color: #ffffff; 11 | border: 2px solid black; 12 | border-radius: 10px; 13 | } 14 | 15 | #export { 16 | position: absolute; 17 | right: 10px; 18 | top: 10px; 19 | background-color: rgb(228,225,218); 20 | border: 1px solid rgb(196, 196, 196); 21 | padding: 10px; 22 | z-index: 3000; 23 | } 24 | 25 | #search { 26 | position: relative; 27 | margin: 10px auto; 28 | background-color: rgb(228,225,218); 29 | border: 1px solid rgb(196, 196, 196); 30 | padding: 10px; 31 | z-index: 3000; 32 | width: 300px; 33 | } 34 | 35 | #search input { 36 | width: 240px; 37 | } 38 | 39 | .typeahead { 40 | background-color: #fff; 41 | } 42 | 43 | .tt-dropdown-menu { 44 | width: 240px; 45 | padding: 8px 0; 46 | background-color: #fff; 47 | border: 1px solid #ccc; 48 | } 49 | 50 | .tt-suggestion { 51 | padding: 3px 20px; 52 | font-size: 18px; 53 | line-height: 24px; 54 | } 55 | 56 | .tt-suggestion.tt-is-under-cursor { 57 | color: #fff; 58 | background-color: #0097cf; 59 | 60 | } 61 | 62 | .tt-suggestion p { 63 | margin: 0; 64 | } 65 | 66 | #export input { 67 | width: 70px; 68 | } -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | /* 2 | Routing capability using the Leaflet framework 3 | Copyright (c) 2013, Turistforeningen, Hans Kristian Flaatten 4 | 5 | https://github.com/Turistforeningen/leaflet-routing 6 | */ 7 | 8 | var routing, data; 9 | 10 | (function() { 11 | "use strict"; 12 | jQuery(function($) { 13 | var api, apiKey, rUrl, sUrl, topo, map, snapping, inport, myRouter; 14 | 15 | api = window.location.hash.substr(1).split('@'); 16 | if (api.length === 2) { 17 | rUrl = 'http://' + api[1] + '/route/?coords=' 18 | sUrl = 'http://' + api[1] + '/bbox/?bbox='; 19 | apiKey = api[0]; 20 | } else { 21 | throw new Error('API auth failed'); 22 | } 23 | 24 | topo = L.tileLayer('http://opencache.statkart.no/gatekeeper/gk/gk.open_gmaps?layers=topo2&zoom={z}&x={x}&y={y}', { 25 | maxZoom: 16, 26 | attribution: 'Statens kartverk' 27 | }); 28 | 29 | var summer = L.tileLayer('http://mt3.turistforeningen.no/prod/trail_summer/{z}/{x}/{y}.png', { 30 | maxZoom: 16, 31 | attribution: 'DNT' 32 | }); 33 | var winter = L.tileLayer('http://mt3.turistforeningen.no/prod/trail_winter/{z}/{x}/{y}.png', { 34 | maxZoom: 16, 35 | attribution: 'DNT' 36 | }); 37 | var cabin = L.tileLayer('http://mt3.turistforeningen.no/prod/cabin/{z}/{x}/{y}.png', { 38 | maxZoom: 16, 39 | attribution: 'DNT' 40 | }); 41 | 42 | map = new L.Map('map', { 43 | layers: [topo] 44 | ,center: new L.LatLng(61.5, 9) 45 | ,zoom: 13 46 | }); 47 | cabin.addTo(map); 48 | summer.addTo(map); 49 | 50 | L.control.layers({'Topo 2': topo}, { 51 | 'DNTs merkede stier': summer 52 | ,'DNTs merkede vinterruter': winter 53 | ,'DNTs turisthytter': cabin 54 | }, { 55 | position: 'topleft' 56 | }).addTo(map); 57 | 58 | // Import Layer 59 | inport = new L.layerGroup(null, { 60 | style: { 61 | opacity:0.5 62 | ,clickable:false 63 | } 64 | }).addTo(map); 65 | 66 | // Snapping Layer 67 | snapping = new L.geoJson(null, { 68 | style: { 69 | opacity:0 70 | ,clickable:false 71 | } 72 | }).addTo(map); 73 | map.on('moveend', function() { 74 | if (map.getZoom() > 12) { 75 | var url; 76 | url = sUrl + map.getBounds().toBBoxString() + '&callback=?'; 77 | $.getJSON(url).always(function(data, status) { 78 | if (status === 'success') { 79 | data = JSON.parse(data); 80 | if (data.geometries && data.geometries.length > 0) { 81 | snapping.clearLayers(); 82 | snapping.addData(data); 83 | } 84 | } else { 85 | console.error('Could not load snapping data'); 86 | } 87 | }); 88 | } else { 89 | snapping.clearLayers(); 90 | } 91 | }); 92 | map.fire('moveend'); 93 | 94 | // Routing Function 95 | // @todo speed up geometryToLayer() 96 | myRouter = function(l1, l2, cb) { 97 | var req = $.getJSON(rUrl + [l1.lng, l1.lat, l2.lng, l2.lat].join(',') + '&callback=?'); 98 | req.always(function(data, status) { 99 | if (status === 'success') { 100 | try { 101 | L.GeoJSON.geometryToLayer(JSON.parse(data)).eachLayer(function (layer) { 102 | // 14026 103 | var d1 = l1.distanceTo(layer._latlngs[0]); 104 | var d2 = l2.distanceTo(layer._latlngs[layer._latlngs.length-1]); 105 | 106 | if (d1 < 10 && d2 < 10) { 107 | return cb(null, layer); 108 | } else { 109 | return cb(new Error('This has been discarded')); 110 | } 111 | }); 112 | } catch(e) { 113 | return cb(new Error('Invalid JSON')); 114 | } 115 | } else { 116 | return cb(new Error('Routing failed')); 117 | } 118 | }); 119 | } 120 | 121 | // Leaflet Routing Module 122 | routing = new L.Routing({ 123 | position: 'topleft' 124 | ,routing: { 125 | router: myRouter 126 | } 127 | ,snapping: { 128 | layers: [snapping] 129 | ,sensitivity: 15 130 | ,vertexonly: false 131 | } 132 | }); 133 | map.addControl(routing); 134 | routing.draw(true); // enable drawing mode 135 | 136 | $('#eta-export').hide(); 137 | $('#eta-export').on('click', function() { 138 | var id = $('#eta-id').val(); 139 | if (!id) { alert('Ingen tp_id definert!'); return; } 140 | if (confirm('Eksport til ETA vil overskrive eksisterende geometri!')) { 141 | var coords = routing.toGeoJSON().coordinates; 142 | var data = []; 143 | for (var i = 0; i < coords.length; i++) { 144 | data.push(coords[i][0] + ' ' + coords[i][1]); 145 | } 146 | data = 'LINESTRING(' + data.join(',') + ')'; 147 | $.post('http://mintur.ut.no/lib/ajax/post_geom.php?api_key=' + apiKey + '&tp_id=' + id, {coords: data}, function(data) { 148 | if (data.error) { 149 | alert('Eksport feilet med feilkode ' + data.error); 150 | } else if (data.success) { 151 | window.location.href = 'http://mintur.ut.no/index.php?tp_id=' + id + '&tab=kart'; 152 | //alert('Eksport suksess!'); 153 | } 154 | }); 155 | } 156 | }); 157 | 158 | $('#eta-import').on('click', function() { 159 | var id = $('#eta-id').val(); 160 | if (!id) { alert('Ingen tp_id definert!'); return; } 161 | $.get('http://mintur.ut.no/lib/ajax/post_geom.php?api_key=' + apiKey + '&tp_id=' + id, function(data) { 162 | if (data.error) { 163 | alert('Import feilet med feilkode ' + data.error); 164 | } else if (typeof data.coords !== 'undefined') { 165 | $('#eta-import').hide(); 166 | $('#eta-export').show(); 167 | $('#eta-id').attr('readonly', 'readonly'); 168 | 169 | if (data.coords) { 170 | data.coords = data.coords.replace('LINESTRING(', '').replace(')', '').split(','); 171 | for (var i = 0; i < data.coords.length; i++) { 172 | data.coords[i] = new L.LatLng(data.coords[i].split(' ')[1], data.coords[i].split(' ')[0]); 173 | } 174 | inport.clearLayers(); 175 | var p = new L.Polyline(data.coords, {clickable:false, color: '#000000', opacity: 0.4}); 176 | inport.addLayer(p); 177 | map.fitBounds(p.getBounds()); 178 | } 179 | } 180 | }); 181 | }); 182 | 183 | function fetchSsrAc(search, cb) { 184 | var result = []; 185 | $.ajax({ 186 | url: "https://ws.geonorge.no/SKWS3Index/ssr/sok?navn=" + search + "*&epsgKode=4326&antPerSide=10" 187 | ,type: "GET" 188 | ,dataType: 'xml' 189 | ,success: function(xml) { 190 | $(xml).find('sokRes > stedsnavn').each(function(){ 191 | result.push({ 192 | title: $(this).find('stedsnavn').text() 193 | ,lat: $(this).find('aust').text() 194 | ,lng: $(this).find('nord').text() 195 | }); 196 | }); 197 | cb(null, result); 198 | } 199 | }); 200 | } 201 | 202 | $('#ssr-search').typeahead({ 203 | remote: { 204 | url: 'https://ws.geonorge.no/SKWS3Index/ssr/sok?navn=%QUERY*&epsgKode=4326&antPerSide=10', 205 | dataType: 'xml', 206 | filter: function(xml) { 207 | var result = []; 208 | $(xml).find('sokRes > stedsnavn').each(function(){ 209 | result.push({ 210 | value: $(this).find('stedsnavn').text() 211 | ,tokens: [$(this).find('stedsnavn').text()] 212 | ,lat: $(this).find('nord').text() 213 | ,lng: $(this).find('aust').text() 214 | }); 215 | }); 216 | return result; 217 | } 218 | } 219 | }); 220 | 221 | $('#ssr-search').on('typeahead:selected', function(e, object) { 222 | var ll = new L.LatLng(object.lat, object.lng); 223 | map.panTo(ll); 224 | $('#ssr-search').val(''); 225 | }) 226 | 227 | }); 228 | }).call(this); 229 | -------------------------------------------------------------------------------- /examples/load-geojson.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Load Leaflet Route 6 | 7 | 8 | 9 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /examples/load-waypoints.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Load Leaflet Route 6 | 7 | 8 | 9 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /examples/osm.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Load Leaflet Route 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 146 | 147 | 148 |
149 |
150 | Fork me on GitHub 151 |
152 |
153 | 154 | 155 | -------------------------------------------------------------------------------- /examples/route.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "LineString", 3 | "properties": { 4 | "waypoints":[ 5 | {"coordinates":[8.906856536865227,61.498998536981695],"_index":0}, 6 | {"coordinates":[8.949394226074219,61.507221451655326],"_index":77}, 7 | {"coordinates":[8.991113039526608,61.49597606714658],"_index":196}, 8 | {"coordinates":[9.03364562988282,61.48669286650201],"_index":380} 9 | ] 10 | }, 11 | "coordinates": [ 12 | [8.9069297439802,61.4989743830191,1132.92067320133], 13 | [8.90724154784554,61.4991902326406,1136], 14 | [8.9073347415908,61.4993519090339,1139], 15 | [8.90740820891501,61.4995996899242,1140], 16 | [8.90740822323507,61.4996284167131,1140], 17 | [8.90740780822136,61.5000001270589,1145], 18 | [8.90740761622789,61.5001508631871,1149], 19 | [8.90755689802269,61.5004741730185,1156], 20 | [8.907706829915,61.5006357808669,1158], 21 | [8.90793506333067,61.5007613660566,1162], 22 | [8.908719614027,61.5011930223032,1170], 23 | [8.90911379871622,61.5012920935566,1169], 24 | [8.90935782548616,61.5013372483362,1169], 25 | [8.91016542493687,61.5014364714851,1177], 26 | [8.91086731277077,61.5014626956094,1188], 27 | [8.9108980004749,61.5014638002919,1188], 28 | [8.91125484094649,61.5014910427938,1194], 29 | [8.91179070416959,61.501642198231,1202], 30 | [8.91189303108492,61.501671005317,1213], 31 | [8.91234342784011,61.5018419232006,1227], 32 | [8.9124679384612,61.5018652398011,1227], 33 | [8.91300322716584,61.5019655760685,1242], 34 | [8.91354506932991,61.5020671040829,1249], 35 | [8.91437101405918,61.5023189469589,1248], 36 | [8.91493379544784,61.5025617044141,1264], 37 | [8.91513540753681,61.5026127836712,1264], 38 | [8.91508019114435,61.5029472785175,1272], 39 | [8.91493041961669,61.5032027087888,1276], 40 | [8.91522847444286,61.5037163693465,1279], 41 | [8.91528011038667,61.5037557814443,1281], 42 | [8.91579240151426,61.5041466573161,1290], 43 | [8.91661543668598,61.5043675569542,1302], 44 | [8.91664498447798,61.5043754530057,1302], 45 | [8.91749314806559,61.5046278183029,1306], 46 | [8.91849269666844,61.5051252223107,1312], 47 | [8.91902719035745,61.5054973564794,1321], 48 | [8.9193703042181,61.5057362986623,1325], 49 | [8.92024312438816,61.5063710172345,1341], 50 | [8.92046413153294,61.5064261967684,1341], 51 | [8.92128904277547,61.5066322008265,1350], 52 | [8.92248007666191,61.5066615935927,1361], 53 | [8.92272053249859,61.5066540640505,1361], 54 | [8.9237344862281,61.5066223997986,1380], 55 | [8.92419130783537,61.5065667275886,1386], 56 | [8.92444964747572,61.5065352308777,1394], 57 | [8.92495656187703,61.506508629673,1397], 58 | [8.92537003649824,61.5064818131266,1400], 59 | [8.92587523724855,61.5065140239652,1408], 60 | [8.92636556218724,61.5065452080322,1409], 61 | [8.92666608289049,61.5066082521347,1411], 62 | [8.92741771891687,61.506617789008,1415], 63 | [8.92822832190934,61.5068470630479,1419], 64 | [8.92896243525351,61.5069452267982,1425], 65 | [8.92929743165783,61.5069900838579,1429], 66 | [8.93058732915021,61.5070238198913,1440], 67 | [8.93071179269098,61.5070369410871,1443], 68 | [8.9317599045293,61.5071476675804,1452], 69 | [8.93304929793769,61.50720522022,1468], 70 | [8.93373725069322,61.5072359222206,1479], 71 | [8.93467202440819,61.5072776337074,1488], 72 | [8.93551692928634,61.5073153294592,1497], 73 | [8.93632780418334,61.5073400658517,1510], 74 | [8.93720239957265,61.5073666487621,1518], 75 | [8.93808841675107,61.5073859011643,1528], 76 | [8.93928803122822,61.5074120158237,1540], 77 | [8.93983582061693,61.5074038401956,1548], 78 | [8.94168863572099,61.5073761179655,1568], 79 | [8.94175476380828,61.5073752830476,1568], 80 | [8.94370094794695,61.5073519646437,1588], 81 | [8.94463757742485,61.5073407418929,1601], 82 | [8.94544017714414,61.5073219224119,1612], 83 | [8.94604018516534,61.5073078533028,1618], 84 | [8.94679433749858,61.5073122615528,1629], 85 | [8.94788323011605,61.5073185120465,1644], 86 | [8.94832097471587,61.507299549956,1649], 87 | [8.94924110289473,61.5072598265395,1664], 88 | [8.94927970373115,61.5072561418717,1664], 89 | [8.94940374245659,61.5072444228199,1665.55374551147], 90 | [8.94940374245659,61.5072444228199,1665.55374551147], 91 | [8.94999819117708,61.5071882582123,1673], 92 | [8.95061278931188,61.5071302452717,1682], 93 | [8.95068189820639,61.507127647697,1682], 94 | [8.95176764196467,61.5070861825043,1694], 95 | [8.95245746916518,61.5069863951584,1698], 96 | [8.95254279612833,61.5069740727506,1703], 97 | [8.95335750699792,61.5068746138703,1713], 98 | [8.9538248354617,61.5068488460621,1719], 99 | [8.95390312540864,61.5068445841548,1719], 100 | [8.95460058478887,61.5067790732135,1729], 101 | [8.95515028531726,61.5066564724723,1737], 102 | [8.95558540447445,61.5067089573853,1742], 103 | [8.95585462754512,61.5067413683642,1746], 104 | [8.95670478398229,61.5067520615147,1754], 105 | [8.95733546052668,61.5065765031147,1756], 106 | [8.95772479110708,61.5063521252765,1755], 107 | [8.95823224561218,61.5062624826857,1756], 108 | [8.95847658131743,61.5061816751737,1756], 109 | [8.95873983801271,61.5060472303528,1757], 110 | [8.95904072003225,61.5058588988233,1758], 111 | [8.95937929292895,61.5056792651961,1758], 112 | [8.95982134428754,61.5054791961039,1759], 113 | [8.96012183764927,61.505248253627,1761], 114 | [8.96027919659453,61.505147550545,1761], 115 | [8.9604787516479,61.5050198173819,1763], 116 | [8.96119760554611,61.5045360620929,1768], 117 | [8.96201145966002,61.5040462583682,1770], 118 | [8.96242535267018,61.5038308841753,1770], 119 | [8.96285744769709,61.503660439633,1769], 120 | [8.96312079917417,61.5033731845482,1771], 121 | [8.96325262502132,61.503014174679,1775], 122 | [8.96334712155468,61.5026281141329,1771], 123 | [8.96327233723375,61.5022869421441,1770], 124 | [8.96314107116482,61.5021073244907,1769], 125 | [8.96306225960671,61.5018517868017,1758], 126 | [8.96299133902331,61.5016225326134,1745], 127 | [8.96302924592967,61.5014431346624,1741], 128 | [8.96302923850336,61.5014247650448,1741], 129 | [8.9630294152674,61.5012097441754,1734], 130 | [8.96308595383206,61.5010750259196,1726], 131 | [8.9629938246816,61.5009490123951,1717], 132 | [8.9627108498035,61.5005632049287,1701], 133 | [8.96270773014781,61.50053371151,1701], 134 | [8.96269226185313,61.5003836388653,1693], 135 | [8.96255402212372,61.500044239831,1676], 136 | [8.96254245731751,61.5000155418674,1676], 137 | [8.96254003538683,61.5000018374058,1676], 138 | [8.96253963542417,61.5000000187283,1676], 139 | [8.96250501189147,61.4998089348361,1670], 140 | [8.96261796648882,61.4996001840432,1667], 141 | [8.96282519625632,61.4992167002429,1661], 142 | [8.96283372368003,61.4991883544859,1661], 143 | [8.96291946575032,61.4989024864642,1656], 144 | [8.96308886073253,61.498660224215,1651], 145 | [8.96297617504252,61.4985973197726,1654], 146 | [8.96297633160079,61.4984715331226,1654], 147 | [8.96306571320519,61.4982764125692,1653], 148 | [8.96320230410041,61.497978035623,1660], 149 | [8.96344669816698,61.4977715207258,1661], 150 | [8.96365350281189,61.4976459077086,1664], 151 | [8.96408565297273,61.4974843791342,1669], 152 | [8.96431144884736,61.4972959465643,1671], 153 | [8.96440582874866,61.4969907418607,1663], 154 | [8.96425563239032,61.4968291036206,1656], 155 | [8.96425283879386,61.4968269985583,1656], 156 | [8.96378643829354,61.4964699651978,1647], 157 | [8.96376799470904,61.4962993203228,1645], 158 | [8.9636178715126,61.4960569136346,1645], 159 | [8.96358039014819,61.4958863235788,1648], 160 | [8.96376881563027,61.4954375317511,1650], 161 | [8.96395694343983,61.495213119839,1644], 162 | [8.96406986082974,61.4949617755454,1643], 163 | [8.96416598406799,61.4948520471465,1633], 164 | [8.96444597698707,61.4945310439627,1630], 165 | [8.96445960906351,61.4945200335177,1630], 166 | [8.96502873943452,61.4940644656725,1599], 167 | [8.96516033590238,61.4939786432603,1599], 168 | [8.96544222234442,61.4937951318045,1592], 169 | [8.96647572634666,61.4933878221411,1575], 170 | [8.96662626123677,61.4933285922549,1575], 171 | [8.96736268323587,61.4930472068955,1556], 172 | [8.9673775661897,61.4930413843098,1556], 173 | [8.96822295992948,61.4927274633143,1547], 174 | [8.96878643743225,61.4926467590212,1541], 175 | [8.96948151676365,61.492503368239,1540], 176 | [8.97003419578306,61.4924374874363,1540], 177 | [8.97053345689313,61.4923779687801,1538], 178 | [8.97143490997508,61.4922882968022,1534], 179 | [8.97203602426383,61.4921717905464,1524], 180 | [8.9724681373013,61.4920282463389,1520], 181 | [8.9725936394155,61.4919998659466,1520], 182 | [8.9731442368169,61.4918757118332,1514], 183 | [8.97370783105705,61.4917796855988,1503], 184 | [8.97445923987694,61.4916515548802,1496], 185 | [8.9754541414524,61.4916965650061,1491], 186 | [8.97611152387083,61.4917685675796,1490], 187 | [8.9766185405275,61.4918942319263,1485], 188 | [8.97736981296426,61.4921727189933,1482], 189 | [8.97757320874444,61.4922846533096,1477], 190 | [8.97825192466734,61.4926576174232,1468], 191 | [8.9788715417362,61.4928283315364,1465], 192 | [8.97971674998501,61.4929451050428,1462], 193 | [8.98054290031764,61.4931067712706,1459], 194 | [8.98112500430261,61.493250510534,1461], 195 | [8.9813092968433,61.4933065757615,1455], 196 | [8.98195127195717,61.4935020366105,1450], 197 | [8.98328446087164,61.4941036694874,1441], 198 | [8.9834258400851,61.4941712482235,1435], 199 | [8.98448616053739,61.4946783075007,1427], 200 | [8.98487475671412,61.4949235872721,1419], 201 | [8.98536864531148,61.4952351152447,1412], 202 | [8.98604474531648,61.49547744676,1403], 203 | [8.98654780110099,61.4955598548621,1401], 204 | [8.98741570074812,61.4957019829283,1391], 205 | [8.98812684499371,61.4958052960784,1381], 206 | [8.98846767599249,61.4958547684471,1378], 207 | [8.9898761468938,61.4959177799776,1367], 208 | [8.99111582069343,61.4959548894561,1362.18762779351], 209 | [8.99111582069343,61.4959548894561,1362.18762779351], 210 | [8.99167935785628,61.4959717551128,1360], 211 | [8.99243382083955,61.4960109586499,1359], 212 | [8.99254331421909,61.4960166087958,1359], 213 | [8.99425268914357,61.4961963333471,1355], 214 | [8.99599951240412,61.4963130395054,1350], 215 | [8.9971449808589,61.4962592765983,1354], 216 | [8.99797150133284,61.4962503072953,1356], 217 | [8.9991360734623,61.4963130756719,1357], 218 | [9.00000003545876,61.4963579715502,1357], 219 | [9.00080775011647,61.4963400509417,1356], 220 | [9.00167165493822,61.4963940278008,1354], 221 | [9.00298636787146,61.496420798981,1349], 222 | [9.00377527983553,61.4964387681443,1343], 223 | [9.00436472052291,61.4964721548143,1337], 224 | [9.00456428570176,61.4964835692779,1337], 225 | [9.00516540583799,61.4965374568704,1335], 226 | [9.00664927527571,61.4965643597726,1331], 227 | [9.00824578509538,61.4965552432164,1331], 228 | [9.00916617652415,61.4965371977445,1330], 229 | [9.00999264888937,61.4964923165785,1324], 230 | [9.01029562605648,61.4964686618512,1320], 231 | [9.01080017900536,61.4964293978947,1312], 232 | [9.01234019745649,61.4965548766363,1305], 233 | [9.01241178614437,61.4965548789287,1305], 234 | [9.01296037800935,61.4965549140421,1296], 235 | [9.01380557623777,61.4965907009653,1287], 236 | [9.01427522413097,61.4966445198037,1285], 237 | [9.01478227288738,61.4967251953681,1287], 238 | [9.01564634234743,61.4967970075535,1281], 239 | [9.01566291413554,61.4967989977618,1281], 240 | [9.01675473491036,61.4969315230601,1276], 241 | [9.01778773735423,61.4969850961455,1272], 242 | [9.01827604274259,61.4967338542882,1262], 243 | [9.01847051344314,61.4966641675251,1262], 244 | [9.01865165634138,61.4965991163435,1257], 245 | [9.01895196766046,61.4964464772665,1255], 246 | [9.01978541602949,61.4958918552821,1239], 247 | [9.01981566137008,61.495871756441,1239], 248 | [9.02094206628705,61.4952072930575,1222], 249 | [9.02126033418766,61.4949690909886,1220], 250 | [9.02137391390197,61.4948840469901,1220], 251 | [9.02216247813233,61.4943722786557,1206], 252 | [9.02246349743775,61.4941531245024,1200], 253 | [9.02259409399383,61.4940579332442,1198], 254 | [9.02311958534234,61.4935102794243,1183], 255 | [9.02340169744097,61.4932943439793,1182], 256 | [9.0234951269874,61.4932229089196,1175], 257 | [9.02375771843558,61.4929175151101,1173], 258 | [9.02451933645731,61.4923632226201,1162], 259 | [9.0246211896173,61.492289095962,1151], 260 | [9.0246774437181,61.4922081922972,1151], 261 | [9.02462091107483,61.4919479224007,1145], 262 | [9.02475227227038,61.4918222396476,1145], 263 | [9.02492127792592,61.4917593489546,1143], 264 | [9.02517309735208,61.4917065817505,1138], 265 | [9.02552204448995,61.491633593125,1132], 266 | [9.02590912825821,61.4914837653528,1120], 267 | [9.02591647065884,61.4914808466782,1120], 268 | [9.0259445049688,61.4914584885809,1120], 269 | [9.02602921951723,61.4913911710275,1118], 270 | [9.02610395963496,61.4911935775949,1105], 271 | [9.02602864575644,61.4910769068783,1107], 272 | [9.02580321624447,61.490906366262,1099], 273 | [9.02581210263078,61.4908572349989,1099], 274 | [9.02584068526527,61.4906998413066,1094], 275 | [9.02600939573414,61.4904484832224,1089], 276 | [9.02629106027278,61.4903227434567,1085], 277 | [9.02663171245015,61.4901977951476,1083], 278 | [9.02717333392733,61.4899993825843,1070], 279 | [9.02769916942535,61.4899633361514,1068], 280 | [9.02826113521727,61.4898481098484,1068], 281 | [9.02825647363774,61.4898037856006,1065], 282 | [9.02822482664488,61.489752873164,1065], 283 | [9.0282138337161,61.4897316809934,1065], 284 | [9.028204729297,61.4897006674511,1065], 285 | [9.02820479168934,61.4896709571215,1065], 286 | [9.02822984160589,61.4896288388768,1061], 287 | [9.02826371486735,61.4895898095393,1061], 288 | [9.02828139023279,61.4895742890728,1061], 289 | [9.02829773895371,61.4895597907838,1061], 290 | [9.02833225724867,61.4895369969063,1061], 291 | [9.02839090447766,61.4895062593334,1061], 292 | [9.0284458162216,61.4894753575962,1058], 293 | [9.02853183316076,61.4894296155074,1058], 294 | [9.02857603287845,61.4894054461961,1058], 295 | [9.02860290841044,61.4893832168296,1058], 296 | [9.02863491324676,61.4893441053231,1058], 297 | [9.02867906493025,61.4892614081555,1055], 298 | [9.02872287304177,61.4891804966756,1055], 299 | [9.02874643526866,61.4891265178187,1055], 300 | [9.0287582009055,61.4890947105932,1052], 301 | [9.02877779764652,61.489061356631,1052], 302 | [9.02880265400124,61.4890300345618,1052], 303 | [9.02882987235117,61.4890060194378,1052], 304 | [9.02885623237053,61.488986468573,1052], 305 | [9.02888546938413,61.488971546093,1052], 306 | [9.02892166202282,61.4889596304132,1052], 307 | [9.02896345759772,61.4889479609074,1052], 308 | [9.02901220879953,61.4889392981907,1049], 309 | [9.02904294952789,61.4889361468897,1049], 310 | [9.02906961164012,61.488934617169,1049], 311 | [9.02910069565913,61.488929680148,1049], 312 | [9.02913757476507,61.4889141929952,1047], 313 | [9.02917784584361,61.4889006556557,1047], 314 | [9.02925293614005,61.4888823453624,1047], 315 | [9.02930883491131,61.4888658930248,1047], 316 | [9.0293808763159,61.4888438471305,1047], 317 | [9.02944697139463,61.4888233407414,1047], 318 | [9.02957184199715,61.4887910102477,1045], 319 | [9.0296523834307,61.48876393529,1045], 320 | [9.02973361124804,61.4887332888647,1042], 321 | [9.02986648690137,61.4886887042791,1041], 322 | [9.02994992520257,61.4886563540584,1041], 323 | [9.03001637992197,61.4886339723871,1041], 324 | [9.03008142726403,61.488618912094,1041], 325 | [9.03015108507102,61.4885994622968,1041], 326 | [9.03016989367177,61.4885937156726,1041], 327 | [9.030195090584,61.4885860887344,1041], 328 | [9.03024012574954,61.488567358025,1038], 329 | [9.0302778619343,61.4885474062729,1038], 330 | [9.03030591667515,61.4885288300084,1038], 331 | [9.03033837299908,61.4885167498527,1038], 332 | [9.03037166726626,61.4885101088642,1038], 333 | [9.03044130458946,61.4885005623663,1038], 334 | [9.03053539270067,61.4884911894903,1038], 335 | [9.03060876520009,61.4884818069842,1036], 336 | [9.03067331430337,61.4884693356826,1036], 337 | [9.03074110378604,61.4884498035366,1036], 338 | [9.03078324121043,61.4884363477413,1036], 339 | [9.03082267292329,61.4884173707237,1036], 340 | [9.03093727232565,61.4883404686195,1034], 341 | [9.03102686598209,61.4882858783304,1032], 342 | [9.03106782179698,61.488268768983,1032], 343 | [9.0311165713794,61.4882601054907,1032], 344 | [9.03121490857689,61.4882482176244,1032], 345 | [9.03127112856868,61.4882398821951,1032], 346 | [9.03132142228705,61.488223182914,1032], 347 | [9.03135915736476,61.4882032308473,1029], 348 | [9.03139929449998,61.4881707787671,1028], 349 | [9.03144488255307,61.4881295621534,1028], 350 | [9.03150337218976,61.4880898125615,1028], 351 | [9.03158821916261,61.4880305115147,1025], 352 | [9.03165078677971,61.4879891401881,1025], 353 | [9.03168255574634,61.4879806311284,1025], 354 | [9.03177189696688,61.487966547013,1023], 355 | [9.03185681664275,61.4879558702285,1023], 356 | [9.03194446167625,61.4879408111188,1023], 357 | [9.03201173454633,61.4879239568888,1023], 358 | [9.03209259523647,61.4879049981729,1023], 359 | [9.03215578963742,61.4878897655514,1020], 360 | [9.03221099891167,61.4878768834121,1020], 361 | [9.03222673276473,61.4878714516966,1020], 362 | [9.0322714873087,61.4878561295512,1019], 363 | [9.03231278498131,61.4878372340949,1019], 364 | [9.03237159662007,61.487805601852,1019], 365 | [9.03243721175155,61.4877679656072,1019], 366 | [9.03252274223738,61.4877050925057,1016], 367 | [9.03257696052088,61.4876777604409,1016], 368 | [9.03263135024885,61.4876495354937,1016], 369 | [9.03267554424689,61.4876253647743,1016], 370 | [9.03270698881937,61.4876087377221,1016], 371 | [9.03275356489388,61.487581970406,1016], 372 | [9.03279299432366,61.4875629927868,1016], 373 | [9.03283394822221,61.4875458828823,1016], 374 | [9.03288949967581,61.4875312147364,1014], 375 | [9.03294062979272,61.4875199539955,1014], 376 | [9.03300586211729,61.4875039101091,1014], 377 | [9.03306871221606,61.4874904627669,1013], 378 | [9.0331082936697,61.4874804956577,1013], 379 | [9.03315094334507,61.4874643605175,1013], 380 | [9.03319395518804,61.4874365361769,1013], 381 | [9.03330618208695,61.4873523250632,1012], 382 | [9.03333068942883,61.4873227878082,1010], 383 | [9.03334267651434,61.4872701910336,1010], 384 | [9.03336083812757,61.4871854512745,1010], 385 | [9.03337558843776,61.4871086652578,1008], 386 | [9.03338673687268,61.4870506293131,1008], 387 | [9.03339868554567,61.4870178394641,1008], 388 | [9.03342355462943,61.4869766129986,1008], 389 | [9.0334815821988,61.4869098306729,1005], 390 | [9.03351001656723,61.4868774945332,1005], 391 | [9.03358759381415,61.4867893308682,1005], 392 | [9.03367368215039,61.4866989705824,1002.04090700385] 393 | ] 394 | } 395 | 396 | -------------------------------------------------------------------------------- /examples/styling.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Styling example 6 | 7 | 8 | 9 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /images/promo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Turistforeningen/leaflet-routing/b7368272b0e32cd43f74e52cd47684297394f79e/images/promo.gif -------------------------------------------------------------------------------- /images/promo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Turistforeningen/leaflet-routing/b7368272b0e32cd43f74e52cd47684297394f79e/images/promo.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Routing in Leaflet 6 | 7 | 8 | 9 | 20 | 21 | 22 | 25 | 26 |
27 | tp_id: 28 | 29 | 30 |
31 |
32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /libs/leaflet/images/layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Turistforeningen/leaflet-routing/b7368272b0e32cd43f74e52cd47684297394f79e/libs/leaflet/images/layers.png -------------------------------------------------------------------------------- /libs/leaflet/images/marker-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Turistforeningen/leaflet-routing/b7368272b0e32cd43f74e52cd47684297394f79e/libs/leaflet/images/marker-icon.png -------------------------------------------------------------------------------- /libs/leaflet/images/marker-icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Turistforeningen/leaflet-routing/b7368272b0e32cd43f74e52cd47684297394f79e/libs/leaflet/images/marker-icon@2x.png -------------------------------------------------------------------------------- /libs/leaflet/images/marker-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Turistforeningen/leaflet-routing/b7368272b0e32cd43f74e52cd47684297394f79e/libs/leaflet/images/marker-shadow.png -------------------------------------------------------------------------------- /libs/leaflet/leaflet.css: -------------------------------------------------------------------------------- 1 | /* required styles */ 2 | 3 | .leaflet-map-pane, 4 | .leaflet-tile, 5 | .leaflet-marker-icon, 6 | .leaflet-marker-shadow, 7 | .leaflet-tile-pane, 8 | .leaflet-tile-container, 9 | .leaflet-overlay-pane, 10 | .leaflet-shadow-pane, 11 | .leaflet-marker-pane, 12 | .leaflet-popup-pane, 13 | .leaflet-overlay-pane svg, 14 | .leaflet-zoom-box, 15 | .leaflet-image-layer, 16 | .leaflet-layer { 17 | position: absolute; 18 | left: 0; 19 | top: 0; 20 | } 21 | .leaflet-container { 22 | overflow: hidden; 23 | -ms-touch-action: none; 24 | } 25 | .leaflet-tile, 26 | .leaflet-marker-icon, 27 | .leaflet-marker-shadow { 28 | -webkit-user-select: none; 29 | -moz-user-select: none; 30 | user-select: none; 31 | -webkit-user-drag: none; 32 | } 33 | .leaflet-marker-icon, 34 | .leaflet-marker-shadow { 35 | display: block; 36 | } 37 | /* map is broken in FF if you have max-width: 100% on tiles */ 38 | .leaflet-container img { 39 | max-width: none !important; 40 | } 41 | /* stupid Android 2 doesn't understand "max-width: none" properly */ 42 | .leaflet-container img.leaflet-image-layer { 43 | max-width: 15000px !important; 44 | } 45 | .leaflet-tile { 46 | filter: inherit; 47 | visibility: hidden; 48 | } 49 | .leaflet-tile-loaded { 50 | visibility: inherit; 51 | } 52 | .leaflet-zoom-box { 53 | width: 0; 54 | height: 0; 55 | } 56 | 57 | .leaflet-tile-pane { z-index: 2; } 58 | .leaflet-objects-pane { z-index: 3; } 59 | .leaflet-overlay-pane { z-index: 4; } 60 | .leaflet-shadow-pane { z-index: 5; } 61 | .leaflet-marker-pane { z-index: 6; } 62 | .leaflet-popup-pane { z-index: 7; } 63 | 64 | 65 | /* control positioning */ 66 | 67 | .leaflet-control { 68 | position: relative; 69 | z-index: 7; 70 | pointer-events: auto; 71 | } 72 | .leaflet-top, 73 | .leaflet-bottom { 74 | position: absolute; 75 | z-index: 1000; 76 | pointer-events: none; 77 | } 78 | .leaflet-top { 79 | top: 0; 80 | } 81 | .leaflet-right { 82 | right: 0; 83 | } 84 | .leaflet-bottom { 85 | bottom: 0; 86 | } 87 | .leaflet-left { 88 | left: 0; 89 | } 90 | .leaflet-control { 91 | float: left; 92 | clear: both; 93 | } 94 | .leaflet-right .leaflet-control { 95 | float: right; 96 | } 97 | .leaflet-top .leaflet-control { 98 | margin-top: 10px; 99 | } 100 | .leaflet-bottom .leaflet-control { 101 | margin-bottom: 10px; 102 | } 103 | .leaflet-left .leaflet-control { 104 | margin-left: 10px; 105 | } 106 | .leaflet-right .leaflet-control { 107 | margin-right: 10px; 108 | } 109 | 110 | 111 | /* zoom and fade animations */ 112 | 113 | .leaflet-fade-anim .leaflet-tile, 114 | .leaflet-fade-anim .leaflet-popup { 115 | opacity: 0; 116 | -webkit-transition: opacity 0.2s linear; 117 | -moz-transition: opacity 0.2s linear; 118 | -o-transition: opacity 0.2s linear; 119 | transition: opacity 0.2s linear; 120 | } 121 | .leaflet-fade-anim .leaflet-tile-loaded, 122 | .leaflet-fade-anim .leaflet-map-pane .leaflet-popup { 123 | opacity: 1; 124 | } 125 | 126 | .leaflet-zoom-anim .leaflet-zoom-animated { 127 | -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1); 128 | -moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1); 129 | -o-transition: -o-transform 0.25s cubic-bezier(0,0,0.25,1); 130 | transition: transform 0.25s cubic-bezier(0,0,0.25,1); 131 | } 132 | .leaflet-zoom-anim .leaflet-tile, 133 | .leaflet-pan-anim .leaflet-tile, 134 | .leaflet-touching .leaflet-zoom-animated { 135 | -webkit-transition: none; 136 | -moz-transition: none; 137 | -o-transition: none; 138 | transition: none; 139 | } 140 | 141 | .leaflet-zoom-anim .leaflet-zoom-hide { 142 | visibility: hidden; 143 | } 144 | 145 | 146 | /* cursors */ 147 | 148 | .leaflet-clickable { 149 | cursor: pointer; 150 | } 151 | .leaflet-container { 152 | cursor: -webkit-grab; 153 | cursor: -moz-grab; 154 | } 155 | .leaflet-popup-pane, 156 | .leaflet-control { 157 | cursor: auto; 158 | } 159 | .leaflet-dragging, 160 | .leaflet-dragging .leaflet-clickable, 161 | .leaflet-dragging .leaflet-container { 162 | cursor: move; 163 | cursor: -webkit-grabbing; 164 | cursor: -moz-grabbing; 165 | } 166 | 167 | 168 | /* visual tweaks */ 169 | 170 | .leaflet-container { 171 | background: #ddd; 172 | outline: 0; 173 | } 174 | .leaflet-container a { 175 | color: #0078A8; 176 | } 177 | .leaflet-container a.leaflet-active { 178 | outline: 2px solid orange; 179 | } 180 | .leaflet-zoom-box { 181 | border: 2px dotted #05f; 182 | background: white; 183 | opacity: 0.5; 184 | } 185 | 186 | 187 | /* general typography */ 188 | .leaflet-container { 189 | font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif; 190 | } 191 | 192 | 193 | /* general toolbar styles */ 194 | 195 | .leaflet-bar { 196 | box-shadow: 0 1px 7px rgba(0,0,0,0.65); 197 | -webkit-border-radius: 4px; 198 | border-radius: 4px; 199 | } 200 | .leaflet-bar a { 201 | background-color: #fff; 202 | border-bottom: 1px solid #ccc; 203 | width: 26px; 204 | height: 26px; 205 | line-height: 26px; 206 | display: block; 207 | text-align: center; 208 | text-decoration: none; 209 | color: black; 210 | } 211 | .leaflet-bar a, 212 | .leaflet-control-layers-toggle { 213 | background-position: 50% 50%; 214 | background-repeat: no-repeat; 215 | display: block; 216 | } 217 | .leaflet-bar a:hover { 218 | background-color: #f4f4f4; 219 | } 220 | .leaflet-bar a:first-child { 221 | -webkit-border-top-left-radius: 4px; 222 | border-top-left-radius: 4px; 223 | -webkit-border-top-right-radius: 4px; 224 | border-top-right-radius: 4px; 225 | } 226 | .leaflet-bar a:last-child { 227 | -webkit-border-bottom-left-radius: 4px; 228 | border-bottom-left-radius: 4px; 229 | -webkit-border-bottom-right-radius: 4px; 230 | border-bottom-right-radius: 4px; 231 | border-bottom: none; 232 | } 233 | .leaflet-bar a.leaflet-disabled { 234 | cursor: default; 235 | background-color: #f4f4f4; 236 | color: #bbb; 237 | } 238 | 239 | .leaflet-touch .leaflet-bar { 240 | -webkit-border-radius: 10px; 241 | border-radius: 10px; 242 | } 243 | .leaflet-touch .leaflet-bar a { 244 | width: 30px; 245 | height: 30px; 246 | } 247 | .leaflet-touch .leaflet-bar a:first-child { 248 | -webkit-border-top-left-radius: 7px; 249 | border-top-left-radius: 7px; 250 | -webkit-border-top-right-radius: 7px; 251 | border-top-right-radius: 7px; 252 | } 253 | .leaflet-touch .leaflet-bar a:last-child { 254 | -webkit-border-bottom-left-radius: 7px; 255 | border-bottom-left-radius: 7px; 256 | -webkit-border-bottom-right-radius: 7px; 257 | border-bottom-right-radius: 7px; 258 | border-bottom: none; 259 | } 260 | 261 | 262 | /* zoom control */ 263 | 264 | .leaflet-control-zoom-in { 265 | font: bold 18px 'Lucida Console', Monaco, monospace; 266 | } 267 | .leaflet-control-zoom-out { 268 | font: bold 22px 'Lucida Console', Monaco, monospace; 269 | } 270 | 271 | .leaflet-touch .leaflet-control-zoom-in { 272 | font-size: 22px; 273 | line-height: 30px; 274 | } 275 | .leaflet-touch .leaflet-control-zoom-out { 276 | font-size: 28px; 277 | line-height: 30px; 278 | } 279 | 280 | 281 | /* layers control */ 282 | 283 | .leaflet-control-layers { 284 | box-shadow: 0 1px 7px rgba(0,0,0,0.4); 285 | background: #f8f8f9; 286 | -webkit-border-radius: 8px; 287 | border-radius: 8px; 288 | } 289 | .leaflet-control-layers-toggle { 290 | background-image: url(images/layers.png); 291 | width: 36px; 292 | height: 36px; 293 | } 294 | .leaflet-touch .leaflet-control-layers-toggle { 295 | width: 44px; 296 | height: 44px; 297 | } 298 | .leaflet-control-layers .leaflet-control-layers-list, 299 | .leaflet-control-layers-expanded .leaflet-control-layers-toggle { 300 | display: none; 301 | } 302 | .leaflet-control-layers-expanded .leaflet-control-layers-list { 303 | display: block; 304 | position: relative; 305 | } 306 | .leaflet-control-layers-expanded { 307 | padding: 6px 10px 6px 6px; 308 | color: #333; 309 | background: #fff; 310 | } 311 | .leaflet-control-layers-selector { 312 | margin-top: 2px; 313 | position: relative; 314 | top: 1px; 315 | } 316 | .leaflet-control-layers label { 317 | display: block; 318 | } 319 | .leaflet-control-layers-separator { 320 | height: 0; 321 | border-top: 1px solid #ddd; 322 | margin: 5px -10px 5px -6px; 323 | } 324 | 325 | 326 | /* attribution and scale controls */ 327 | 328 | .leaflet-container .leaflet-control-attribution { 329 | background-color: rgba(255, 255, 255, 0.7); 330 | box-shadow: 0 0 5px #bbb; 331 | margin: 0; 332 | } 333 | .leaflet-control-attribution, 334 | .leaflet-control-scale-line { 335 | padding: 0 5px; 336 | color: #333; 337 | } 338 | .leaflet-container .leaflet-control-attribution, 339 | .leaflet-container .leaflet-control-scale { 340 | font-size: 11px; 341 | } 342 | .leaflet-left .leaflet-control-scale { 343 | margin-left: 5px; 344 | } 345 | .leaflet-bottom .leaflet-control-scale { 346 | margin-bottom: 5px; 347 | } 348 | .leaflet-control-scale-line { 349 | border: 2px solid #777; 350 | border-top: none; 351 | color: black; 352 | line-height: 1.1; 353 | padding: 2px 5px 1px; 354 | font-size: 11px; 355 | text-shadow: 1px 1px 1px #fff; 356 | background-color: rgba(255, 255, 255, 0.5); 357 | box-shadow: 0 -1px 5px rgba(0, 0, 0, 0.2); 358 | white-space: nowrap; 359 | overflow: hidden; 360 | } 361 | .leaflet-control-scale-line:not(:first-child) { 362 | border-top: 2px solid #777; 363 | border-bottom: none; 364 | margin-top: -2px; 365 | box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); 366 | } 367 | .leaflet-control-scale-line:not(:first-child):not(:last-child) { 368 | border-bottom: 2px solid #777; 369 | } 370 | 371 | .leaflet-touch .leaflet-control-attribution, 372 | .leaflet-touch .leaflet-control-layers, 373 | .leaflet-touch .leaflet-control-zoom { 374 | box-shadow: none; 375 | } 376 | .leaflet-touch .leaflet-control-layers, 377 | .leaflet-touch .leaflet-control-zoom { 378 | border: 4px solid rgba(0,0,0,0.3); 379 | } 380 | 381 | 382 | /* popup */ 383 | 384 | .leaflet-popup { 385 | position: absolute; 386 | text-align: center; 387 | } 388 | .leaflet-popup-content-wrapper { 389 | padding: 1px; 390 | text-align: left; 391 | -webkit-border-radius: 20px; 392 | border-radius: 20px; 393 | } 394 | .leaflet-popup-content { 395 | margin: 14px 20px; 396 | line-height: 1.4; 397 | } 398 | .leaflet-popup-content p { 399 | margin: 18px 0; 400 | } 401 | .leaflet-popup-tip-container { 402 | margin: 0 auto; 403 | width: 40px; 404 | height: 20px; 405 | position: relative; 406 | overflow: hidden; 407 | } 408 | .leaflet-popup-tip { 409 | width: 15px; 410 | height: 15px; 411 | padding: 1px; 412 | 413 | margin: -8px auto 0; 414 | 415 | -webkit-transform: rotate(45deg); 416 | -moz-transform: rotate(45deg); 417 | -ms-transform: rotate(45deg); 418 | -o-transform: rotate(45deg); 419 | transform: rotate(45deg); 420 | } 421 | .leaflet-popup-content-wrapper, .leaflet-popup-tip { 422 | background: white; 423 | 424 | box-shadow: 0 3px 14px rgba(0,0,0,0.4); 425 | } 426 | .leaflet-container a.leaflet-popup-close-button { 427 | position: absolute; 428 | top: 0; 429 | right: 0; 430 | padding: 4px 5px 0 0; 431 | text-align: center; 432 | width: 18px; 433 | height: 14px; 434 | font: 16px/14px Tahoma, Verdana, sans-serif; 435 | color: #c3c3c3; 436 | text-decoration: none; 437 | font-weight: bold; 438 | background: transparent; 439 | } 440 | .leaflet-container a.leaflet-popup-close-button:hover { 441 | color: #999; 442 | } 443 | .leaflet-popup-scrolled { 444 | overflow: auto; 445 | border-bottom: 1px solid #ddd; 446 | border-top: 1px solid #ddd; 447 | } 448 | 449 | 450 | /* div icon */ 451 | 452 | .leaflet-div-icon { 453 | background: #fff; 454 | border: 1px solid #666; 455 | } 456 | .leaflet-editing-icon { 457 | -webkit-border-radius: 2px; 458 | border-radius: 2px; 459 | } 460 | -------------------------------------------------------------------------------- /libs/leaflet/leaflet.ie.css: -------------------------------------------------------------------------------- 1 | .leaflet-vml-shape { 2 | width: 1px; 3 | height: 1px; 4 | } 5 | .lvml { 6 | behavior: url(#default#VML); 7 | display: inline-block; 8 | position: absolute; 9 | } 10 | 11 | .leaflet-control { 12 | display: inline; 13 | } 14 | 15 | .leaflet-popup-tip { 16 | width: 21px; 17 | _width: 27px; 18 | margin: 0 auto; 19 | _margin-top: -3px; 20 | 21 | filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678); 22 | -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)"; 23 | } 24 | .leaflet-popup-tip-container { 25 | margin-top: -1px; 26 | } 27 | .leaflet-popup-content-wrapper, .leaflet-popup-tip { 28 | border: 1px solid #999; 29 | } 30 | .leaflet-popup-content-wrapper { 31 | zoom: 1; 32 | } 33 | 34 | .leaflet-control-zoom, 35 | .leaflet-control-layers { 36 | border: 3px solid #999; 37 | } 38 | .leaflet-control-layers-toggle { 39 | } 40 | .leaflet-control-attribution, 41 | .leaflet-control-layers, 42 | .leaflet-control-scale-line { 43 | background: white; 44 | } 45 | .leaflet-zoom-box { 46 | filter: alpha(opacity=50); 47 | } 48 | .leaflet-control-attribution { 49 | border-top: 1px solid #bbb; 50 | border-left: 1px solid #bbb; 51 | } 52 | -------------------------------------------------------------------------------- /src/L.Routing.Draw.js: -------------------------------------------------------------------------------- 1 | /* 2 | * L.Routing.Draw class 3 | * 4 | * Responsible for drawing and contine drawing 5 | * 6 | * @dependencies L, L.Routing 7 | * 8 | * @usage new L.Routing.Draw(map, options); 9 | */ 10 | 11 | L.Routing.Draw = L.Handler.extend({ 12 | 13 | // INCLUDES 14 | includes: [L.Mixin.Events] 15 | 16 | // OPTIONS 17 | ,options: {} 18 | 19 | /** 20 | * Draw Constructor 21 | * 22 | * @access public 23 | * 24 | * @param <> parent - parent class instance 25 | * @param options - routing options 26 | * 27 | * @return void 28 | * 29 | * @todo fetch last waypoint 30 | */ 31 | ,initialize: function (parent, options) { 32 | this._parent = parent; 33 | this._map = parent._map; 34 | 35 | this._enabled = false; 36 | 37 | L.Util.setOptions(this, options); 38 | } 39 | 40 | /** 41 | * Enable drawing 42 | * 43 | * @access public 44 | * 45 | * @event map.routing:draw-start 46 | * @event map.routing:draw-new 47 | * @event map.routing:draw-continue 48 | * 49 | * @return void 50 | */ 51 | ,enable: function() { 52 | if (this._enabled) { return; } 53 | 54 | this._enabled = true; 55 | this._hidden = false; 56 | this._dragging = false; 57 | this._addHooks(); 58 | this.fire('enabled'); 59 | 60 | this._map.fire('routing:draw-start'); 61 | if (this._parent._segments._layers.length === 0) { 62 | this._map.fire('routing:draw-new'); 63 | } else { 64 | this._map.fire('routing:draw-continue'); 65 | } 66 | } 67 | 68 | /** 69 | * Disable drawing 70 | * 71 | * @access public 72 | * 73 | * @event map.routing:draw-end 74 | * 75 | * @return void 76 | */ 77 | ,disable: function() { 78 | if (!this._enabled) { return; } 79 | 80 | this._enabled = false; 81 | this._removeHooks(); 82 | this.fire('disabled'); 83 | 84 | this._map.fire('routing:draw-end'); 85 | } 86 | 87 | /** 88 | * Add hooks 89 | * 90 | * @access private 91 | * 92 | * @return void 93 | */ 94 | ,_addHooks: function() { 95 | if (!this._map) { return; } 96 | 97 | // Visible Marker 98 | if (!this._marker) { 99 | this._marker = new L.Marker(this._map.getCenter(), { 100 | icon: (this.options.icons.draw ? this.options.icons.draw : new L.Icon.Default()) 101 | ,opacity: (this.options.icons.draw ? 1.0 : 0.0) 102 | ,zIndexOffset: this.options.zIndexOffset 103 | ,clickable: false 104 | }); 105 | } 106 | 107 | // Trailing line 108 | if (!this._trailer) { 109 | var ll = this._map.getCenter(); 110 | this._trailerOpacity = this.options.styles.trailer.opacity || 0.2; 111 | var style = L.extend({}, this.options.styles.trailer, { 112 | opacity: 0.0 113 | ,clickable: false 114 | }); 115 | this._trailer = new L.Polyline([ll, ll], style); 116 | } 117 | 118 | this._parent.on('waypoint:mouseover', this._catchWaypointEvent, this); 119 | this._parent.on('waypoint:mouseout' , this._catchWaypointEvent, this); 120 | this._parent.on('waypoint:dragstart', this._catchWaypointEvent, this); 121 | this._parent.on('waypoint:dragend' , this._catchWaypointEvent, this); 122 | 123 | this._parent.on('segment:mouseover' , this._catchWaypointEvent, this); 124 | this._parent.on('segment:mouseout' , this._catchWaypointEvent, this); 125 | this._parent.on('segment:dragstart' , this._catchWaypointEvent, this); 126 | this._parent.on('segment:dragend' , this._catchWaypointEvent, this); 127 | 128 | this._map.on('mousemove', this._onMouseMove, this); 129 | this._map.on('click', this._onMouseClick, this); 130 | 131 | this._marker.addTo(this._map); 132 | this._trailer.addTo(this._map); 133 | } 134 | 135 | /** 136 | * Remove hooks 137 | * 138 | * This method is invoked after the `disable()` has been called and removes 139 | * all the hooks set up using the `_addHooks()` method. 140 | * 141 | * @access private 142 | * 143 | * @return void 144 | */ 145 | ,_removeHooks: function() { 146 | if (!this._map) { return; } 147 | 148 | this._parent.off('waypoint:mouseover', this._catchWaypointEvent, this); 149 | this._parent.off('waypoint:mouseout' , this._catchWaypointEvent, this); 150 | this._parent.off('waypoint:dragstart', this._catchWaypointEvent, this); 151 | this._parent.off('waypoint:dragend' , this._catchWaypointEvent, this); 152 | 153 | this._parent.off('segment:mouseover' , this._catchWaypointEvent, this); 154 | this._parent.off('segment:mouseout' , this._catchWaypointEvent, this); 155 | this._parent.off('segment:dragstart' , this._catchWaypointEvent, this); 156 | this._parent.off('segment:dragend' , this._catchWaypointEvent, this); 157 | 158 | this._map.off('click', this._onMouseClick, this); 159 | this._map.off('mousemove', this._onMouseMove, this); 160 | 161 | this._map.removeLayer(this._marker); 162 | this._map.removeLayer(this._trailer); 163 | 164 | delete this._marker; 165 | delete this._trailer; 166 | } 167 | 168 | /** 169 | * Handle waypoint events 170 | * 171 | * @access private 172 | * 173 | * @param e - waypoint event 174 | * 175 | * @return void 176 | */ 177 | ,_catchWaypointEvent: function(e) { 178 | var type = e.type.split(':')[1]; 179 | 180 | if (this._hidden) { 181 | if (this._dragging) { 182 | if (type === 'dragend') { 183 | this._dragging = false; 184 | } 185 | } else { 186 | if (type === 'mouseout') { 187 | this._show(); 188 | } else if (type === 'dragstart') { 189 | this._dragging = true; 190 | } 191 | } 192 | } else { 193 | if (type === 'mouseover') { 194 | this._hide(); 195 | } 196 | } 197 | } 198 | 199 | /** 200 | * Hide HUD 201 | * 202 | * Call this method in order to quickly hide graphical drawing elements for 203 | * instance hoovering over draggable objects which should tempoarily disable 204 | * dragging. 205 | * 206 | * @access private 207 | * 208 | * @return void 209 | */ 210 | ,_hide: function() { 211 | this._hidden = true; 212 | this._marker.setOpacity(0.0); 213 | this._trailer.setStyle({opacity: 0.0}); 214 | } 215 | 216 | /** 217 | * Show HUD 218 | * 219 | * Call this method to restore graphical drawing elements after they have been 220 | * hidden. 221 | * 222 | * @access private 223 | * 224 | * @return void 225 | */ 226 | ,_show: function() { 227 | this._hidden = false; 228 | this._marker.setOpacity(this.options.icons.draw ? 1.0 : 0.0); 229 | this._showTrailer(); 230 | } 231 | 232 | /** 233 | * Show trailer when hidden 234 | * 235 | * @access private 236 | * 237 | * @return void 238 | */ 239 | ,_showTrailer: function() { 240 | if (this._trailer.options.opacity === 0.0) { 241 | this._trailer.setStyle({opacity: this._trailerOpacity}); 242 | } 243 | } 244 | 245 | /** 246 | * Set trailing guide line 247 | * 248 | */ 249 | ,_setTrailer: function(fromLatLng, toLatLng) { 250 | this._trailer.setLatLngs([fromLatLng, toLatLng]); 251 | this._showTrailer(); 252 | } 253 | 254 | /** 255 | * Mouse move handler 256 | * 257 | * @access private 258 | * 259 | * @param e - mouse move event 260 | * 261 | * @return void 262 | */ 263 | ,_onMouseMove : function(e) { 264 | if (this._hidden) { return; } 265 | 266 | var latlng = e.latlng; 267 | var last = this._parent.getLast(); 268 | 269 | if (this.options.snapping) { 270 | latlng = L.LineUtil.snapToLayers(latlng, null, this.options.snapping); 271 | } 272 | 273 | this._marker.setLatLng(latlng); 274 | 275 | 276 | if (last !== null) { 277 | this._setTrailer(last.getLatLng(), latlng); 278 | }; 279 | } 280 | 281 | /** 282 | * Mouse click handler 283 | * 284 | * @access private 285 | * 286 | * @param e - mouse click event 287 | * 288 | * @event map.routing:new-waypoint 289 | * 290 | * @return void 291 | */ 292 | ,_onMouseClick: function(e) { 293 | if (this._hidden) { return; } 294 | 295 | var marker, latlng, last; 296 | 297 | latlng = e.latlng; 298 | if (this.options.snapping) { 299 | latlng = L.LineUtil.snapToLayers(latlng, null, this.options.snapping); 300 | } 301 | marker = new L.Marker(latlng, {title: this.options.tooltips.waypoint }); 302 | last = this._parent.getLast(); 303 | 304 | this._setTrailer(latlng, latlng); 305 | this._parent.addWaypoint(marker, last, null, function(err, data) { 306 | // console.log(err, data); 307 | }); 308 | } 309 | }); 310 | -------------------------------------------------------------------------------- /src/L.Routing.Edit.js: -------------------------------------------------------------------------------- 1 | /* 2 | * L.Routing.Edit class 3 | * 4 | * Responsible handle edits 5 | * 6 | * @dependencies L, L.Routing 7 | * 8 | * @usage new L.Routing.Draw(map, options); 9 | */ 10 | 11 | L.Routing.Edit = L.Handler.extend({ 12 | 13 | // INCLUDES 14 | includes: [L.Mixin.Events] 15 | 16 | // OPTIONS 17 | ,options: {} 18 | 19 | /** 20 | * Edit Constructor 21 | * 22 | * @access public 23 | * 24 | * @param <> parent - parent class instance 25 | * @param options - routing options 26 | * 27 | * @return void 28 | * 29 | * @todo fetch last waypoint 30 | */ 31 | ,initialize: function (parent, options) { 32 | this._parent = parent; 33 | this._map = parent._map; 34 | 35 | this._enabled = false; 36 | 37 | L.Util.setOptions(this, options); 38 | } 39 | 40 | /** 41 | * Enable drawing 42 | * 43 | * @access public 44 | * 45 | * @event map.routing:edit-start 46 | * 47 | * @return void 48 | */ 49 | ,enable: function() { 50 | if (this._enabled) { return; } 51 | 52 | this._enabled = true; 53 | this._addHooks(); 54 | this.fire('enabled'); 55 | 56 | this._map.fire('routing:edit-start'); 57 | } 58 | 59 | /** 60 | * Disable drawing 61 | * 62 | * @access public 63 | * 64 | * @event map.draw:edit-end 65 | * 66 | * @return void 67 | */ 68 | ,disable: function() { 69 | if (!this._enabled) { return; } 70 | 71 | this._enabled = false; 72 | this._removeHooks(); 73 | this.fire('disabled'); 74 | 75 | this._map.fire('routing:edit-end'); 76 | } 77 | 78 | /** 79 | * Add hooks 80 | * 81 | * This method is invoked when `enable()` is called – and sets up all 82 | * necessary hooks such as: 83 | * * text selection 84 | * * key listeners 85 | * * mouse marker 86 | * 87 | * @access private 88 | * 89 | * @return void 90 | * 91 | * @todo hide and style the trailer! 92 | */ 93 | ,_addHooks: function() { 94 | if (!this._map) { return; } 95 | 96 | if (!this._mouseMarker) { 97 | this._mouseMarker = new L.Marker(this._map.getCenter(), { 98 | icon: L.divIcon({ 99 | className: 'line-mouse-marker' 100 | ,iconAnchor: [5, 5] 101 | ,iconSize: [10, 10] 102 | }) 103 | ,clickable: true 104 | ,draggable: true 105 | ,opacity: 0 106 | ,zIndexOffset: this.options.zIndexOffset 107 | ,title: this.options.tooltips.segment 108 | }); 109 | } 110 | this._mouseMarker.addTo(this._map); 111 | 112 | if (!this._trailer1) { 113 | var ll = this._map.getCenter(); 114 | this._trailerOpacity = this.options.styles.trailer.opacity || 0.2; 115 | var style = L.extend({}, this.options.styles.trailer, {opacity: 0.0,clickable: false}); 116 | this._trailer1 = new L.Polyline([ll, ll], style); 117 | this._trailer2 = new L.Polyline([ll, ll], style); 118 | } 119 | this._trailer1.addTo(this._map); 120 | this._trailer2.addTo(this._map); 121 | 122 | this._parent.on('segment:mouseover' , this._segmentOnMouseover, this); 123 | 124 | this._mouseMarker.on('dragstart' , this._segmentOnDragstart, this); 125 | this._mouseMarker.on('drag' , this._segmentOnDrag, this); 126 | this._mouseMarker.on('dragend' , this._segmentOnDragend, this); 127 | 128 | this._parent.on('waypoint:dragstart', this._waypointOnDragstart, this); 129 | this._parent.on('waypoint:drag' , this._waypointOnDrag, this); 130 | this._parent.on('waypoint:dragend' , this._waypointOnDragend, this); 131 | } 132 | 133 | /** 134 | * Remove hooks 135 | * 136 | * This method is invoked after the `disable()` has been called and removes 137 | * all the hooks set up using the `_addHooks()` method. 138 | * 139 | * @access private 140 | * 141 | * @return void 142 | */ 143 | ,_removeHooks: function() { 144 | if (!this._map) { return; } 145 | 146 | // this._trailer1.addTo(this._map); 147 | // this._trailer2.addTo(this._map); 148 | 149 | this._parent.off('segment:mouseover' , this._segmentOnMouseover, this); 150 | 151 | this._mouseMarker.off('dragstart' , this._segmentOnDragstart, this); 152 | this._mouseMarker.off('drag' , this._segmentOnDrag, this); 153 | this._mouseMarker.off('dragend' , this._segmentOnDragend, this); 154 | 155 | this._parent.off('waypoint:dragstart', this._waypointOnDragstart, this); 156 | this._parent.off('waypoint:drag' , this._waypointOnDrag, this); 157 | this._parent.off('waypoint:dragend' , this._waypointOnDragend, this); 158 | } 159 | 160 | /** 161 | * Fired when the mouse first enters a segment 162 | * 163 | * @access private 164 | * 165 | * @param e - mouse over event 166 | * 167 | * @return void 168 | */ 169 | ,_segmentOnMouseover: function(e) { 170 | this._mouseMarker.setOpacity(1.0); 171 | this._map.on('mousemove', this._segmentOnMousemove, this); 172 | } 173 | 174 | /** 175 | * Fired when the mouse leaves a segement 176 | * 177 | * @access private 178 | * 179 | * @param e - mouse move event 180 | * 181 | * @return void 182 | */ 183 | ,_segmentOnMouseout: function(e) { 184 | if (this._dragging) { return; } 185 | 186 | this._mouseMarker.setOpacity(0.0); 187 | this._map.off('mousemove', this._segmentOnMousemove, this); 188 | 189 | this.fire('segment:mouseout'); 190 | } 191 | 192 | /** 193 | * Fired when the mouse is moved 194 | * 195 | * This method is fired continously when mouse is moved in edition mode. 196 | * 197 | * @access private 198 | * 199 | * @param e - mouse move event 200 | * 201 | * @return void 202 | */ 203 | ,_segmentOnMousemove: function(e) { 204 | if (this._dragging) { return; } 205 | 206 | var latlng = L.LineUtil.snapToLayers(e.latlng, null, { 207 | layers: [this._parent._segments] 208 | ,sensitivity: 40 209 | ,vertexonly: false 210 | }); 211 | 212 | if (latlng._feature === null) { 213 | this._segmentOnMouseout(e); 214 | } else { 215 | this._mouseMarker._snapping = latlng._feature._routing; 216 | this._mouseMarker.setLatLng(latlng); 217 | } 218 | } 219 | 220 | /** 221 | * Mouse marker dragstart 222 | * 223 | * @access private 224 | * 225 | * @param e - mouse dragstart event 226 | * 227 | * @return void 228 | */ 229 | ,_segmentOnDragstart: function(e) { 230 | var latlng = e.target.getLatLng(); 231 | var next = e.target._snapping.nextMarker; 232 | var prev = e.target._snapping.prevMarker; 233 | 234 | this._setTrailers(latlng, next, prev, true); 235 | 236 | this._dragging = true; 237 | this.fire('segment:dragstart'); 238 | } 239 | 240 | /** 241 | * Fired when a marker is dragged 242 | * 243 | * This method is fired continously when dragging a marker and snapps the 244 | * marker to the snapping layer. 245 | * 246 | * @access private 247 | * 248 | * @param e - mouse drag event 249 | * 250 | * @return void 251 | */ 252 | ,_segmentOnDrag: function(e) { 253 | var latlng = e.target.getLatLng(); 254 | var next = e.target._snapping.nextMarker; 255 | var prev = e.target._snapping.prevMarker; 256 | 257 | if (this.options.snapping) { 258 | latlng = L.LineUtil.snapToLayers(latlng, null, this.options.snapping); 259 | } 260 | 261 | e.target.setLatLng(latlng); 262 | this._setTrailers(latlng, next, prev); 263 | } 264 | 265 | /** 266 | * Mouse marker dragend 267 | * 268 | * @access private 269 | * 270 | * @param e - mouse dragend event 271 | * 272 | * @return void 273 | */ 274 | ,_segmentOnDragend: function(e) { 275 | var next = this._mouseMarker._snapping.nextMarker; 276 | var prev = this._mouseMarker._snapping.prevMarker; 277 | var latlng = this._mouseMarker.getLatLng(); 278 | 279 | this._parent.addWaypoint(latlng, prev, next, function(err, data) { 280 | //console.log(err, data); 281 | }); 282 | 283 | this._dragging = false; 284 | this._setTrailers(null, null, null, false); 285 | this.fire('segment:dragend'); 286 | } 287 | 288 | /** 289 | * Fired when marker drag start 290 | * 291 | * @access private 292 | * 293 | * @param e - mouse dragend event 294 | * 295 | * @return void 296 | */ 297 | ,_waypointOnDragstart: function(e) { 298 | var next = e.marker._routing.nextMarker; 299 | var prev = e.marker._routing.prevMarker; 300 | 301 | this._setTrailers(e.marker.getLatLng(), next, prev, true); 302 | } 303 | 304 | /** 305 | * Fired while dragging marker 306 | * 307 | * @access private 308 | * 309 | * @access private 310 | * 311 | * @param e - mouse drag event 312 | * 313 | * @return void 314 | */ 315 | ,_waypointOnDrag: function(e) { 316 | var latlng = e.marker._latlng; 317 | var next = e.marker._routing.nextMarker; 318 | var prev = e.marker._routing.prevMarker; 319 | 320 | if (this.options.snapping) { 321 | latlng = L.LineUtil.snapToLayers(latlng, null, this.options.snapping); 322 | } 323 | 324 | e.marker.setLatLng(latlng); 325 | this._setTrailers(latlng, next, prev); 326 | } 327 | 328 | /** 329 | * Fired when marker drag ends 330 | * 331 | * @access private 332 | * 333 | * @param e - mouse dragend event 334 | * 335 | * @return void 336 | */ 337 | ,_waypointOnDragend: function(e) { 338 | this._setTrailers(null, null, null, false); 339 | this._parent.routeWaypoint(e.marker, function(err, data) { 340 | //console.log('_waypointOnDragend.cb', err, data); 341 | }); 342 | } 343 | 344 | /** 345 | * Fired when marker is clicked 346 | * 347 | * This method is fired when a marker is clicked by the user. It will then 348 | * procede to remove the marker and reroute any connected line segments. 349 | * 350 | * @access private 351 | * 352 | * @param e - mouse click event 353 | * 354 | * @return void 355 | */ 356 | ,_waypointOnClick: function(e) { 357 | this._parent.removeWaypoint(e.layer, function(err, data) { 358 | //console.log('_waypointOnDragend.cb', err, data); 359 | }); 360 | } 361 | 362 | /** 363 | * Set trailing guide lines 364 | * 365 | */ 366 | ,_setTrailers: function(latlng, next, prev, show) { 367 | if (typeof show !== 'undefined') { 368 | if (show === false) { 369 | this._trailer1.setStyle({opacity: 0.0}); 370 | this._trailer2.setStyle({opacity: 0.0}); 371 | return; 372 | } else { 373 | if (next !== null) { 374 | this._trailer1.setStyle({opacity: this._trailerOpacity}); 375 | } 376 | if (prev !== null) { 377 | this._trailer2.setStyle({opacity: this._trailerOpacity}); 378 | } 379 | } 380 | } 381 | if (next) { 382 | this._trailer1.setLatLngs([latlng, next.getLatLng()]); 383 | } 384 | if (prev) { 385 | this._trailer2.setLatLngs([latlng, prev.getLatLng()]); 386 | } 387 | } 388 | }); 389 | 390 | -------------------------------------------------------------------------------- /src/L.Routing.Storage.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Leaflet Routing Storage 3 | * 4 | * Storing routable objects 5 | * 6 | * @dependencies L, L.Routing 7 | * 8 | * @usage new L.Routing(options); 9 | */ 10 | 11 | (function () { 12 | L.Routing.Storage = L.MultiPolyline.extend({ 13 | /** 14 | * Class constructor 15 | */ 16 | initialize: function (latlngs, options) { 17 | this._layers = {}; 18 | this._options = options; 19 | this.setLatLngs(latlngs); 20 | 21 | this.on('layeradd', function() { 22 | console.log('layeradd', arguments); 23 | }, this); 24 | } 25 | }); 26 | 27 | L.Routing.storage = function (latlngs, options) { 28 | return new L.MultiPolyline(latlngs, options); 29 | }; 30 | 31 | }()); 32 | -------------------------------------------------------------------------------- /src/L.Routing.js: -------------------------------------------------------------------------------- 1 | /* 2 | * L.Routing main class 3 | * 4 | * Main clase for the Leaflet routing module 5 | * 6 | * @dependencies L 7 | * 8 | * @usage new L.Routing(options); 9 | * 10 | * @todo use L.Class.extend instead? 11 | */ 12 | 13 | L.Routing = L.Control.extend({ 14 | 15 | // INCLUDES 16 | includes: [L.Mixin.Events] 17 | 18 | // CONSTANTS 19 | ,statics: { 20 | VERSION: '0.1.1-dev' 21 | } 22 | 23 | // OPTIONS 24 | ,options: { 25 | position: 'topleft' 26 | ,tooltips: { 27 | waypoint: 'Waypoint. Drag to move; Click to remove.', 28 | segment: 'Drag to create a new waypoint' 29 | } 30 | ,icons: { 31 | start: new L.Icon.Default() 32 | ,end: new L.Icon.Default() 33 | ,normal: new L.Icon.Default() 34 | ,draw: new L.Icon.Default() 35 | } 36 | ,styles: { 37 | trailer: {} 38 | ,track: {} 39 | ,nodata: {} 40 | } 41 | ,zIndexOffset: 2000 42 | ,routing: { 43 | router: null // function ( l1, l2, cb) 44 | } 45 | ,snapping: { 46 | layers: [] // layers to snap to 47 | ,sensitivity: 10 // snapping sensitivity 48 | ,vertexonly: false // vertex only snapping 49 | } 50 | ,shortcut: { 51 | draw: { 52 | enable: 68, // char code for 'd' 53 | disable: 81 // char code for 'q' 54 | } 55 | } 56 | } 57 | 58 | /** 59 | * Routing Constructor 60 | * 61 | * @access public 62 | * 63 | * @param options - non-default options 64 | * 65 | * @todo render display of segments and waypoints 66 | */ 67 | ,initialize: function (options) { 68 | this._editing = false; 69 | this._drawing = false; 70 | 71 | L.Util.setOptions(this, options); 72 | } 73 | 74 | /** 75 | * Called when controller is added to map 76 | * 77 | * @access public 78 | * 79 | * @param map - map instance 80 | * 81 | * @return container 82 | */ 83 | ,onAdd: function (map) { 84 | this._map = map; 85 | this._container = this._map._container; 86 | this._overlayPane = this._map._panes.overlayPane; 87 | this._popupPane = this._map._panes.popupPane; 88 | 89 | this._router = this.options.routing.router; 90 | this._segments = new L.FeatureGroup().addTo(map); 91 | this._waypoints = new L.FeatureGroup().addTo(map); 92 | this._waypoints._first = null; 93 | this._waypoints._last = null; 94 | 95 | //L.DomUtil.disableTextSelection(); 96 | //this._tooltip = new L.Tooltip(this._map); 97 | //this._tooltip.updateContent({ text: L.drawLocal.draw.marker.tooltip.start }); 98 | 99 | if (this.options.shortcut) { 100 | L.DomEvent.addListener(this._container, 'keyup', this._keyupListener, this); 101 | } 102 | 103 | this._draw = new L.Routing.Draw(this, this.options); 104 | this._edit = new L.Routing.Edit(this, this.options); 105 | this._edit.enable(); 106 | 107 | this.on('waypoint:click', this._waypointClickHandler, this) 108 | this._segments.on('mouseover' , this._fireSegmentEvent, this); 109 | this._edit.on('segment:mouseout' , this._fireSegmentEvent, this); 110 | this._edit.on('segment:dragstart', this._fireSegmentEvent, this); 111 | this._edit.on('segment:dragend' , this._fireSegmentEvent, this); 112 | 113 | var container = L.DomUtil.create('div', 'leaflet-routing'); 114 | 115 | return container; 116 | } 117 | 118 | /** 119 | * Called when controller is removed from map 120 | * 121 | * @access public 122 | * 123 | * @param map - map instance 124 | */ 125 | ,onRemove: function(map) { 126 | //L.DomUtil.create('div', 'leaflet-routing'); <= delete this 127 | 128 | this.off('waypoint:click', this._waypointClickHandler, this) 129 | this._segments.off('mouseover' , this._fireSegmentEvent, this); 130 | this._edit.off('segment:mouseout' , this._fireSegmentEvent, this); 131 | this._edit.off('segment:dragstart', this._fireSegmentEvent, this); 132 | this._edit.off('segment:dragend' , this._fireSegmentEvent, this); 133 | 134 | this._edit.disable(); 135 | this._draw.disable(); 136 | 137 | L.DomUtil.enableTextSelection(); 138 | // this._tooltip.dispose(); 139 | // this._tooltip = null; 140 | L.DomEvent.removeListener(this._container, 'keyup', this._keyupListener); 141 | 142 | delete this._draw; 143 | delete this._edit; 144 | delete this._map; 145 | delete this._router; 146 | delete this._segments; 147 | delete this._waypoints; 148 | delete this.options; 149 | } 150 | 151 | /** 152 | * Called whenever a waypoint is clicked 153 | * 154 | * @access private 155 | * 156 | * @param e - click event 157 | */ 158 | ,_waypointClickHandler: function(e) { 159 | this.removeWaypoint(e.marker, function() { 160 | // console.log(arguments); 161 | }); 162 | } 163 | 164 | /** 165 | * Add new waypoint to path 166 | * 167 | * @access public 168 | * 169 | * @param marker - new waypoint marker (can be ll) 170 | * @param prev - previous waypoint marker 171 | * @param next - next waypoint marker 172 | * @param cb - callback method (err, marker) 173 | * 174 | * @return void 175 | */ 176 | ,addWaypoint: function(marker, prev, next, cb) { 177 | if (marker instanceof L.LatLng) { 178 | marker = new L.Marker(marker, { title: this.options.tooltips.waypoint }); 179 | } 180 | 181 | marker._routing = { 182 | prevMarker : prev 183 | ,nextMarker : next 184 | ,prevLine : null 185 | ,nextLine : null 186 | ,timeoutID : null 187 | }; 188 | 189 | if (this._waypoints._first === null && this._waypoints._last === null) { 190 | this._waypoints._first = marker; 191 | this._waypoints._last = marker; 192 | } else if (next === null) { 193 | this._waypoints._last = marker; 194 | } else if (prev === null) { 195 | this._waypoints._first = marker; 196 | } 197 | 198 | if (marker._routing.prevMarker !== null) { 199 | marker._routing.prevMarker._routing.nextMarker = marker; 200 | marker._routing.prevLine = marker._routing.prevMarker._routing.nextLine; 201 | if (marker._routing.prevLine !== null) { 202 | marker._routing.prevLine._routing.nextMarker = marker; 203 | } 204 | } 205 | 206 | if (marker._routing.nextMarker !== null) { 207 | marker._routing.nextMarker._routing.prevMarker = marker; 208 | marker.nextLine = marker._routing.nextMarker._routing.prevLine; 209 | if (marker._routing.nextLine !== null) { 210 | marker._routing.nextLine._routing.prevMarker = marker; 211 | } 212 | } 213 | 214 | marker.on('mouseover', this._fireWaypointEvent, this); 215 | marker.on('mouseout' , this._fireWaypointEvent, this); 216 | marker.on('dragstart', this._fireWaypointEvent, this); 217 | marker.on('dragend' , this._fireWaypointEvent, this); 218 | marker.on('drag' , this._fireWaypointEvent, this); 219 | marker.on('click' , this._fireWaypointEvent, this); 220 | 221 | this.routeWaypoint(marker, cb); 222 | this._waypoints.addLayer(marker); 223 | marker.dragging.enable(); 224 | } 225 | 226 | /** 227 | * Remove a waypoint from path 228 | * 229 | * @access public 230 | * 231 | * @param marker - new waypoint marker (can be ll) 232 | * @param cb - callback method 233 | * 234 | * @return void 235 | */ 236 | ,removeWaypoint: function(marker, cb) { 237 | marker.off('mouseover', this._fireWaypointEvent, this); 238 | marker.off('mouseout' , this._fireWaypointEvent, this); 239 | marker.off('dragstart', this._fireWaypointEvent, this); 240 | marker.off('dragend' , this._fireWaypointEvent, this); 241 | marker.off('drag' , this._fireWaypointEvent, this); 242 | marker.off('click' , this._fireWaypointEvent, this); 243 | 244 | var prev = marker._routing.prevMarker; 245 | var next = marker._routing.nextMarker; 246 | 247 | if (this._waypoints._first && marker._leaflet_id === this._waypoints._first._leaflet_id) { 248 | this._waypoints._first = next; 249 | } 250 | 251 | if (this._waypoints._last && marker._leaflet_id === this._waypoints._last._leaflet_id) { 252 | this._waypoints._last = prev; 253 | } 254 | 255 | if (prev !== null) { 256 | prev._routing.nextMarker = next; 257 | prev._routing.nextLine = null; 258 | } 259 | 260 | if (next !== null) { 261 | next._routing.prevMarker = prev; 262 | next._routing.prevLine = null; 263 | } 264 | 265 | if (marker._routing.nextLine !== null) { 266 | this._segments.removeLayer(marker._routing.nextLine); 267 | } 268 | 269 | if (marker._routing.prevLine !== null) { 270 | this._segments.removeLayer(marker._routing.prevLine); 271 | } 272 | 273 | this._waypoints.removeLayer(marker); 274 | 275 | if (prev !== null) { 276 | this.routeWaypoint(prev, cb); 277 | } else if (next !== null) { 278 | this.routeWaypoint(next, cb); 279 | } else { 280 | this._draw.enable(); 281 | cb(null, null); 282 | } 283 | 284 | } 285 | 286 | /** 287 | * Route with respect to waypoint 288 | * 289 | * @access public 290 | * 291 | * @param marker - marker to route on 292 | * @param cb - callback function 293 | * 294 | * @return void 295 | * 296 | * @todo add propper error checking for callback 297 | */ 298 | ,routeWaypoint: function(marker, cb) { 299 | var i = 0; 300 | var firstErr; 301 | var $this = this; 302 | var callback = function(err, data) { 303 | i++; 304 | firstErr = firstErr || err; 305 | if (i === 2) { 306 | $this.fire('routing:routeWaypointEnd', { err: firstErr }); 307 | cb(firstErr, marker); 308 | } 309 | } 310 | 311 | this.fire('routing:routeWaypointStart'); 312 | 313 | this._routeSegment(marker._routing.prevMarker, marker, callback); 314 | this._routeSegment(marker, marker._routing.nextMarker, callback); 315 | } 316 | 317 | /** 318 | * Recalculate the complete route by routing each segment 319 | * 320 | * @access public 321 | * 322 | * @param cb - callback function 323 | * 324 | * @return void 325 | * 326 | * @todo add propper error checking for callback 327 | */ 328 | ,rerouteAllSegments: function(cb) { 329 | var numSegments = this.getWaypoints().length - 1; 330 | var callbackCount = 0; 331 | var firstErr; 332 | var $this = this; 333 | 334 | var callback = function(err, data) { 335 | callbackCount++; 336 | firstErr = firstErr || err; 337 | if (callbackCount >= numSegments) { 338 | $this.fire('routing:rerouteAllSegmentsEnd', { err: firstErr }); 339 | if (cb) { 340 | cb(firstErr); 341 | } 342 | } 343 | }; 344 | 345 | $this.fire('routing:rerouteAllSegmentsStart'); 346 | 347 | if (numSegments < 1) { 348 | return callback(null, true); 349 | } 350 | 351 | this._eachSegment(function(m1, m2) { 352 | this._routeSegment(m1, m2, callback); 353 | }); 354 | } 355 | 356 | /** 357 | * Route segment between two markers 358 | * 359 | * @access private 360 | * 361 | * @param m1 - first waypoint marker 362 | * @param m2 - second waypoint marker 363 | * @param cb - callback function ( err, data) 364 | * 365 | * @return void 366 | * 367 | * @todo logic if router fails 368 | */ 369 | ,_routeSegment: function(m1, m2, cb) { 370 | var $this = this; 371 | 372 | if (m1 === null || m2 === null) { 373 | return cb(null, true); 374 | } 375 | 376 | this._router(m1.getLatLng(), m2.getLatLng(), function(err, layer) { 377 | if (typeof layer === 'undefined') { 378 | var layer = new L.Polyline([m1.getLatLng(), m2.getLatLng()], $this.options.styles.nodata); 379 | } else { 380 | layer.setStyle($this.options.styles.track); 381 | } 382 | 383 | layer._routing = { 384 | prevMarker: m1 385 | ,nextMarker: m2 386 | }; 387 | 388 | if (m1._routing.nextLine !== null) { 389 | $this._segments.removeLayer(m1._routing.nextLine); 390 | } 391 | $this._segments.addLayer(layer); 392 | 393 | m1._routing.nextLine = layer; 394 | m2._routing.prevLine = layer; 395 | 396 | return cb(err, layer); 397 | }); 398 | } 399 | 400 | /** 401 | * Iterate over all segments and execute callback for each segment 402 | * 403 | * @access private 404 | * 405 | * @param callback - function to call for each segment 406 | * @param context - callback execution context (this). Optional, default: this 407 | * 408 | * @return void 409 | */ 410 | ,_eachSegment: function(callback, context) { 411 | var thisArg = context || this; 412 | var marker = this.getFirst(); 413 | 414 | if (marker === null) { return; } 415 | 416 | while (marker._routing.nextMarker !== null) { 417 | var m1 = marker; 418 | var m2 = marker._routing.nextMarker; 419 | var line = marker._routing.nextLine; 420 | 421 | callback.call(thisArg, m1, m2, line); 422 | 423 | marker = marker._routing.nextMarker; 424 | } 425 | } 426 | 427 | /** 428 | * Fire events 429 | * 430 | * @access private 431 | * 432 | * @param e - mouse event 433 | * 434 | * @return void 435 | */ 436 | ,_fireWaypointEvent: function(e) { 437 | this.fire('waypoint:' + e.type, {marker:e.target}); 438 | } 439 | 440 | /** 441 | * 442 | */ 443 | ,_fireSegmentEvent: function(e) { 444 | if (e.type.split(':').length === 2) { 445 | this.fire(e.type); 446 | } else { 447 | this.fire('segment:' + e.type); 448 | } 449 | } 450 | 451 | /** 452 | * Get first waypoint 453 | * 454 | * @access public 455 | * 456 | * @return L.Marker 457 | */ 458 | ,getFirst: function() { 459 | return this._waypoints._first; 460 | } 461 | 462 | /** 463 | * Get last waypoint 464 | * 465 | * @access public 466 | * 467 | * @return L.Marker 468 | */ 469 | ,getLast: function() { 470 | return this._waypoints._last; 471 | } 472 | 473 | /** 474 | * Get all waypoints 475 | * 476 | * @access public 477 | * 478 | * @return all waypoints or empty array if none 479 | */ 480 | ,getWaypoints: function() { 481 | var latLngs = []; 482 | 483 | this._eachSegment(function(m1) { 484 | latLngs.push(m1.getLatLng()); 485 | }); 486 | 487 | if (this.getLast()) { 488 | latLngs.push(this.getLast().getLatLng()); 489 | } 490 | 491 | return latLngs; 492 | } 493 | 494 | /** 495 | * Concatenates all route segments to a single polyline 496 | * 497 | * @access public 498 | * 499 | * @return polyline, with empty _latlngs when no route segments 500 | */ 501 | ,toPolyline: function() { 502 | var latLngs = []; 503 | 504 | this._eachSegment(function(m1, m2, line) { 505 | latLngs = latLngs.concat(line.getLatLngs()); 506 | }); 507 | 508 | return L.polyline(latLngs); 509 | } 510 | 511 | /** 512 | * Export route to GeoJSON 513 | * 514 | * @access public 515 | * 516 | * @param enforce2d - enforce 2DGeoJSON 517 | * 518 | * @return GeoJSON object 519 | * 520 | */ 521 | ,toGeoJSON: function(enforce2d) { 522 | var geojson = {type: "LineString", properties: {waypoints: []}, coordinates: []}; 523 | var current = this._waypoints._first; 524 | 525 | if (current === null) { return geojson; } 526 | 527 | // First waypoint marker 528 | geojson.properties.waypoints.push({ 529 | coordinates: [current.getLatLng().lng, current.getLatLng().lat], 530 | _index: 0 531 | }); 532 | 533 | while (current._routing.nextMarker) { 534 | var next = current._routing.nextMarker; 535 | 536 | // Line segment 537 | var tmp = current._routing.nextLine.getLatLngs(); 538 | for (var i = 0; i < tmp.length; i++) { 539 | if (tmp[i].alt && (typeof enforce2d === 'undefined' || enforce2d === false)) { 540 | geojson.coordinates.push([tmp[i].lng, tmp[i].lat, tmp[i].alt]); 541 | } else { 542 | geojson.coordinates.push([tmp[i].lng, tmp[i].lat]); 543 | } 544 | } 545 | 546 | // Waypoint marker 547 | geojson.properties.waypoints.push({ 548 | coordinates: [next.getLatLng().lng, next.getLatLng().lat], 549 | _index: geojson.coordinates.length-1 550 | }); 551 | 552 | // Next waypoint marker 553 | current = current._routing.nextMarker; 554 | } 555 | 556 | return geojson 557 | } 558 | 559 | /** 560 | * Import route from GeoJSON 561 | * 562 | * @access public 563 | * 564 | * @param geojson - GeoJSON object with waypoints 565 | * @param opts - parsing options 566 | * @param cb - callback method (err) 567 | * 568 | * @return undefined 569 | * 570 | */ 571 | ,loadGeoJSON: function(geojson, opts, cb) { 572 | var $this, oldRouter, index, waypoints; 573 | 574 | $this = this; 575 | 576 | // Check for optional options parameter 577 | if (typeof opts === 'function' || typeof opts === 'undefined') { 578 | cb = opts; 579 | opts = {} 580 | } 581 | 582 | // Set default options 583 | opts.waypointDistance = opts.waypointDistance || 50; 584 | opts.fitBounds = opts.fitBounds || true; 585 | 586 | // Check for waypoints before processing geojson 587 | if (!geojson.properties || !geojson.properties.waypoints) { 588 | if (!geojson.properties) { geojson.properties = {} }; 589 | geojson.properties.waypoints = []; 590 | 591 | for (var i = 0; i < geojson.coordinates.length; i = i + opts.waypointDistance) { 592 | geojson.properties.waypoints.push({ 593 | _index: i, 594 | coordinates: geojson.coordinates[i].slice(0, 2) 595 | }); 596 | } 597 | 598 | if (i > geojson.coordinates.length-1) { 599 | geojson.properties.waypoints.push({ 600 | _index: geojson.coordinates.length-1, 601 | coordinates: geojson.coordinates[geojson.coordinates.length-1].slice(0, 2) 602 | }); 603 | } 604 | } 605 | 606 | index = 0; 607 | oldRouter = $this._router; 608 | waypoints = geojson.properties.waypoints; 609 | 610 | // This is a fake router. 611 | // 612 | // It is currently not possible to add a waypoint with a known line segment 613 | // manually. We are hijacking the router so that we can intercept the 614 | // request and return the correct linesegment. 615 | // 616 | // It you want to fix this; please make a patch and submit a pull request on 617 | // GitHub. 618 | $this._router = function(m1, m2, cb) { var start = 619 | waypoints[index-1]._index; var end = waypoints[index]._index+1; 620 | 621 | return cb(null, L.GeoJSON.geometryToLayer({ 622 | type: 'LineString', 623 | coordinates: geojson.coordinates.slice(start, end) 624 | })); 625 | }; 626 | 627 | // Clean up 628 | end = function() { 629 | $this._router = oldRouter; // Restore router 630 | // Set map bounds based on loaded geometry 631 | setTimeout(function() { 632 | if (opts.fitBounds) { 633 | $this._map.fitBounds(L.polyline(L.GeoJSON.coordsToLatLngs(geojson.coordinates)).getBounds()); 634 | } 635 | 636 | if (typeof cb === 'function') { cb(null); } 637 | }, 0); 638 | } 639 | 640 | // Add waypoints 641 | add = function() { 642 | if (!waypoints[index]) { return end() } 643 | 644 | var coords = waypoints[index].coordinates; 645 | var prev = $this._waypoints._last; 646 | 647 | $this.addWaypoint(L.latLng(coords[1], coords[0]), prev, null, function(err, m) { 648 | add(++index); 649 | }); 650 | } 651 | 652 | add(); 653 | } 654 | 655 | /** 656 | * Start (or continue) drawing 657 | * 658 | * Call this method in order to start or continue drawing. The drawing handler 659 | * will be activate and the user can draw on the map. 660 | * 661 | * @access public 662 | * 663 | * @return void 664 | * 665 | * @todo check enable 666 | */ 667 | ,draw: function (enable) { 668 | if (typeof enable === 'undefined') { 669 | var enable = true; 670 | } 671 | 672 | if (enable) { 673 | this._draw.enable(); 674 | } else { 675 | this._draw.disable(); 676 | } 677 | } 678 | 679 | /** 680 | * Enable or disable routing 681 | * 682 | * @access public 683 | * 684 | * @return void 685 | * 686 | * @todo check enable 687 | */ 688 | ,routing: function (enable) { 689 | throw new Error('Not implemented'); 690 | } 691 | 692 | /** 693 | * Enable or disable snapping 694 | * 695 | * @access public 696 | * 697 | * @return void 698 | * 699 | * @todo check enable 700 | */ 701 | ,snapping: function (enable) { 702 | throw new Error('Not implemented'); 703 | } 704 | 705 | /** 706 | * Key up listener 707 | * 708 | * * `ESC` to cancel drawing 709 | * * `M` to enable drawing 710 | * 711 | * @access private 712 | * 713 | * @return void 714 | */ 715 | ,_keyupListener: function (e) { 716 | if (e.keyCode === this.options.shortcut.draw.disable) { 717 | this._draw.disable(); 718 | } else if (e.keyCode === this.options.shortcut.draw.enable) { 719 | this._draw.enable(); 720 | } 721 | } 722 | 723 | }); 724 | -------------------------------------------------------------------------------- /src/utils/LineUtil.Snapping.js: -------------------------------------------------------------------------------- 1 | L.Util.extend(L.LineUtil, { 2 | 3 | /** 4 | * Snap to all layers 5 | * 6 | * @param latlng - original position 7 | * @param id - leaflet unique id 8 | * @param opts - snapping options 9 | * 10 | * @return closest point 11 | */ 12 | snapToLayers: function (latlng, id, opts) { 13 | var i, j, keys, feature, res, sensitivity, vertexonly, layers, minDist, minPoint, map; 14 | 15 | sensitivity = opts.sensitivity || 10; 16 | vertexonly = opts.vertexonly || false; 17 | layers = opts.layers || []; 18 | minDist = Infinity; 19 | minPoint = latlng; 20 | minPoint._feature = null; // containing layer 21 | 22 | if (!opts || !opts.layers || !opts.layers.length) { 23 | return minPoint; 24 | } 25 | 26 | map = opts.layers[0]._map; // @todo check for undef 27 | 28 | for (i = 0; i < opts.layers.length; i++) { 29 | keys = Object.keys(opts.layers[i]._layers); 30 | for (j = 0; j < keys.length; j++) { 31 | feature = opts.layers[i]._layers[keys[j]]; 32 | 33 | // Don't even try snapping to itself! 34 | if (id === feature._leaflet_id) { continue; } 35 | 36 | // GeometryCollection 37 | if (feature._layers) { 38 | var newLatlng = this.snapToLayers(latlng, id, { 39 | 'sensitivity': sensitivity, 40 | 'vertexonly': vertexonly, 41 | 'layers': [feature] 42 | }); 43 | // What if this is the same? 44 | res = {'minDist': latlng.distanceTo(newLatlng), 'minPoint': newLatlng}; 45 | 46 | // Marker 47 | } else if (feature instanceof L.Marker) { 48 | res = this._snapToLatlngs(latlng, [feature.getLatLng()], map, sensitivity, vertexonly, minDist); 49 | 50 | // Polyline 51 | } else if (feature instanceof L.Polyline) { 52 | res = this._snapToLatlngs(latlng, feature.getLatLngs(), map, sensitivity, vertexonly, minDist); 53 | 54 | // MultiPolyline 55 | } else if (feature instanceof L.MultiPolyline) { 56 | console.error('Snapping to MultiPolyline is currently unsupported', feature); 57 | res = {'minDist': minDist, 'minPoint': minPoint}; 58 | 59 | // Polygon 60 | } else if (feature instanceof L.Polygon) { 61 | res = this._snapToPolygon(latlng, feature, map, sensitivity, vertexonly, minDist); 62 | 63 | // MultiPolygon 64 | } else if (feature instanceof L.MultiPolygon) { 65 | res = this._snapToMultiPolygon(latlng, feature, map, sensitivity, vertexonly, minDist); 66 | 67 | // Unknown 68 | } else { 69 | console.error('Unsupported snapping feature', feature); 70 | res = {'minDist': minDist, 'minPoint': minPoint}; 71 | } 72 | 73 | if (res.minDist < minDist) { 74 | minDist = res.minDist; 75 | minPoint = res.minPoint; 76 | minPoint._feature = feature; 77 | } 78 | 79 | } 80 | } 81 | 82 | return minPoint; 83 | }, 84 | 85 | /** 86 | * Snap to Polygon 87 | * 88 | * @param latlng - original position 89 | * @param feature - 90 | * @param map - 91 | * @param sensitivity - 92 | * @param vertexonly - 93 | * @param minDist - 94 | * 95 | * @return minDist and minPoint 96 | */ 97 | _snapToPolygon: function (latlng, polygon, map, sensitivity, vertexonly, minDist) { 98 | var res, keys, latlngs, i, minPoint; 99 | 100 | minPoint = null; 101 | 102 | latlngs = polygon.getLatLngs(); 103 | latlngs.push(latlngs[0]); 104 | res = this._snapToLatlngs(latlng, polygon.getLatLngs(), map, sensitivity, vertexonly, minDist); 105 | if (res.minDist < minDist) { 106 | minDist = res.minDist; 107 | minPoint = res.minPoint; 108 | } 109 | 110 | keys = Object.keys(polygon._holes); 111 | for (i = 0; i < keys.length; i++) { 112 | latlngs = polygon._holes[keys[i]]; 113 | latlngs.push(latlngs[0]); 114 | res = this._snapToLatlngs(latlng, polygon._holes[keys[i]], map, sensitivity, vertexonly, minDist); 115 | if (res.minDist < minDist) { 116 | minDist = res.minDist; 117 | minPoint = res.minPoint; 118 | } 119 | } 120 | 121 | return {'minDist': minDist, 'minPoint': minPoint}; 122 | }, 123 | 124 | /** 125 | * Snap to MultiPolygon 126 | * 127 | * @param latlng - original position 128 | * @param feature - 129 | * @param map - 130 | * @param sensitivity - 131 | * @param vertexonly - 132 | * @param minDist - 133 | * 134 | * @return minDist and minPoint 135 | */ 136 | _snapToMultiPolygon: function (latlng, multipolygon, map, sensitivity, vertexonly, minDist) { 137 | var i, keys, res, minPoint; 138 | 139 | minPoint = null; 140 | 141 | keys = Object.keys(multipolygon._layers); 142 | for (i = 0; i < keys.length; i++) { 143 | res = this._snapToPolygon(latlng, multipolygon._layers[keys[i]], map, sensitivity, vertexonly, minDist); 144 | 145 | if (res.minDist < minDist) { 146 | minDist = res.minDist; 147 | minPoint = res.minPoint; 148 | } 149 | } 150 | 151 | return {'minDist': minDist, 'minPoint': minPoint}; 152 | }, 153 | 154 | 155 | /** 156 | * Snap to of 157 | * 158 | * @param latlng - cursor click 159 | * @param latlngs - array of to snap to 160 | * @param opts - snapping options 161 | * @param isPolygon - if feature is a polygon 162 | * 163 | * @return minDist and minPoint 164 | */ 165 | _snapToLatlngs: function (latlng, latlngs, map, sensitivity, vertexonly, minDist) { 166 | var i, tmpDist, minPoint, p, p1, p2, d2; 167 | 168 | p = map.latLngToLayerPoint(latlng); 169 | p1 = minPoint = null; 170 | 171 | for (i = 0; i < latlngs.length; i++) { 172 | p2 = map.latLngToLayerPoint(latlngs[i]); 173 | 174 | if (!vertexonly && p1 !== null) { 175 | tmpDist = L.LineUtil.pointToSegmentDistance(p, p1, p2); 176 | if (tmpDist < minDist && tmpDist <= sensitivity) { 177 | minDist = tmpDist; 178 | minPoint = map.layerPointToLatLng(L.LineUtil.closestPointOnSegment(p, p1, p2)); 179 | } 180 | } else if ((d2 = p.distanceTo(p2)) && d2 <= sensitivity && d2 < minDist) { 181 | minDist = d2; 182 | minPoint = latlngs[i]; 183 | } 184 | 185 | p1 = p2; 186 | } 187 | 188 | return {'minDist': minDist, 'minPoint': minPoint}; 189 | } 190 | 191 | }); 192 | -------------------------------------------------------------------------------- /src/utils/Marker.Snapping.js: -------------------------------------------------------------------------------- 1 | L.Marker.include({ 2 | /** 3 | * Snap to function 4 | * 5 | * @param latlng - original position 6 | * 7 | * @return - new position 8 | */ 9 | snapTo: function (latlng) { 10 | return L.LineUtil.snapToLayers(latlng, this._leaflet_id, this.options.snapping); 11 | } 12 | }); -------------------------------------------------------------------------------- /src/utils/Polyline.Snapping.js: -------------------------------------------------------------------------------- 1 | L.Polyline.include({ 2 | /** 3 | * Snap to function 4 | * 5 | * @param latlng - original position 6 | * 7 | * @return - new position 8 | */ 9 | snapTo: function (latlng) { 10 | return L.LineUtil.snapToLayers(latlng, this._leaflet_id, this.options.snapping); 11 | } 12 | }); --------------------------------------------------------------------------------