check-in bower_components

This commit is contained in:
Norbert Renner 2014-01-27 18:46:51 +01:00
parent 4cc16bccd0
commit 9e08e74132
101 changed files with 90960 additions and 0 deletions

View file

@ -0,0 +1,13 @@
{
"name": "leaflet-routing",
"homepage": "https://github.com/Turistforeningen/leaflet-routing",
"_release": "9d66697f2a",
"_resolution": {
"type": "branch",
"branch": "gh-pages",
"commit": "9d66697f2a0e547b61a48c7a5358f5f0bba280fe"
},
"_source": "git://github.com/Turistforeningen/leaflet-routing.git",
"_target": "gh-pages",
"_originalSource": "Turistforeningen/leaflet-routing"
}

View file

@ -0,0 +1 @@
*.swp

View file

@ -0,0 +1,22 @@
Copyright (c) 2013, Turistforeningen, Hans Kristian Flaatten
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are
permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of
conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list
of conditions and the following disclaimer in the documentation and/or other materials
provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -0,0 +1,80 @@
Routing in Leaflet
==================
Research and development of a Leaflet.js routing module. The module will be capable of the routing markers and returning geojson in various ways.
![Prototype Routing using Leaflet](https://raw.github.com/Turistforeningen/leaflet-routing/gh-pages/images/promo.gif)
## Usage
```javascript
var routing = new L.Routing({
position: 'topright'
,routing: {
router: myRouter
}
,snapping: {
layers: [snapping]
,sensitivity: 15
,vertexonly: false
}
});
map.addControl(routing);
```
### Enable Drawing
```javascript
routing.draw(true);
```
### Enable Routing `NOT IMPLEMENTED`
```javascript
routing.routing(true);
```
### Enable Snapping `NOT IMPLEMETED`
```javascript
routing.snapping(true);
```
### Get first waypoint
```javascript
var first = routing.getFirst();
```
### Get last waypoint
```javascript
var last = routing.getLast();
```
### Get all waypoints
```javascript
var waypointsArray = routing.getWaypoints();
```
### Routing to Polyline
```javascript
var polyline = routing.toPolyline();
```
### To GeoJSON
```javascript
var geoJSON3D = routing.toGeoJSON();
var geoJSON2D = routing.toGeoJSON(false);
```
## Copyright
Copyright (c) 2013, Turistforeningen
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -0,0 +1,68 @@
#map {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
div.line-mouse-marker {
background-color: #ffffff;
border: 2px solid black;
border-radius: 10px;
}
#export {
position: absolute;
right: 10px;
top: 10px;
background-color: rgb(228,225,218);
border: 1px solid rgb(196, 196, 196);
padding: 10px;
z-index: 3000;
}
#search {
position: relative;
margin: 10px auto;
background-color: rgb(228,225,218);
border: 1px solid rgb(196, 196, 196);
padding: 10px;
z-index: 3000;
width: 300px;
}
#search input {
width: 240px;
}
.typeahead {
background-color: #fff;
}
.tt-dropdown-menu {
width: 240px;
padding: 8px 0;
background-color: #fff;
border: 1px solid #ccc;
}
.tt-suggestion {
padding: 3px 20px;
font-size: 18px;
line-height: 24px;
}
.tt-suggestion.tt-is-under-cursor {
color: #fff;
background-color: #0097cf;
}
.tt-suggestion p {
margin: 0;
}
#export input {
width: 70px;
}

228
bower_components/leaflet-routing/app.js vendored Normal file
View file

@ -0,0 +1,228 @@
/*
Routing capability using the Leaflet framework
Copyright (c) 2013, Turistforeningen, Hans Kristian Flaatten
https://github.com/Turistforeningen/leaflet-routing
*/
var routing, data;
(function() {
"use strict";
jQuery(function($) {
var api, apiKey, rUrl, sUrl, topo, map, snapping, inport, myRouter;
api = window.location.hash.substr(1).split('@');
if (api.length === 2) {
rUrl = 'http://' + api[1] + '/route/?coords='
sUrl = 'http://' + api[1] + '/bbox/?bbox=';
apiKey = api[0];
} else {
throw new Error('API auth failed');
}
topo = L.tileLayer('http://opencache.statkart.no/gatekeeper/gk/gk.open_gmaps?layers=topo2&zoom={z}&x={x}&y={y}', {
maxZoom: 16,
attribution: '<a href="http://www.statkart.no/">Statens kartverk</a>'
});
var summer = L.tileLayer('http://mt3.turistforeningen.no/prod/trail_summer/{z}/{x}/{y}.png', {
maxZoom: 16,
attribution: '<a href="http://www.turistforeningen.no/">DNT</a>'
});
var winter = L.tileLayer('http://mt3.turistforeningen.no/prod/trail_winter/{z}/{x}/{y}.png', {
maxZoom: 16,
attribution: '<a href="http://www.turistforeningen.no/">DNT</a>'
});
var cabin = L.tileLayer('http://mt3.turistforeningen.no/prod/cabin/{z}/{x}/{y}.png', {
maxZoom: 16,
attribution: '<a href="http://www.turistforeningen.no/">DNT</a>'
});
map = new L.Map('map', {
layers: [topo]
,center: new L.LatLng(61.5, 9)
,zoom: 13
});
cabin.addTo(map);
summer.addTo(map);
L.control.layers({'Topo 2': topo}, {
'DNTs merkede stier': summer
,'DNTs merkede vinterruter': winter
,'DNTs turisthytter': cabin
}, {
position: 'topleft'
}).addTo(map);
// Import Layer
inport = new L.layerGroup(null, {
style: {
opacity:0.5
,clickable:false
}
}).addTo(map);
// Snapping Layer
snapping = new L.geoJson(null, {
style: {
opacity:0
,clickable:false
}
}).addTo(map);
map.on('moveend', function() {
if (map.getZoom() > 12) {
var url;
url = sUrl + map.getBounds().toBBoxString() + '&callback=?';
$.getJSON(url).always(function(data, status) {
if (status === 'success') {
data = JSON.parse(data);
if (data.geometries && data.geometries.length > 0) {
snapping.clearLayers();
snapping.addData(data);
}
} else {
console.error('Could not load snapping data');
}
});
} else {
snapping.clearLayers();
}
});
map.fire('moveend');
// Routing Function
// @todo speed up geometryToLayer()
myRouter = function(l1, l2, cb) {
var req = $.getJSON(rUrl + [l1.lng, l1.lat, l2.lng, l2.lat].join(',') + '&callback=?');
req.always(function(data, status) {
if (status === 'success') {
try {
L.GeoJSON.geometryToLayer(JSON.parse(data)).eachLayer(function (layer) {
// 14026
var d1 = l1.distanceTo(layer._latlngs[0]);
var d2 = l2.distanceTo(layer._latlngs[layer._latlngs.length-1]);
if (d1 < 10 && d2 < 10) {
return cb(null, layer);
} else {
return cb(new Error('This has been discarded'));
}
});
} catch(e) {
return cb(new Error('Invalid JSON'));
}
} else {
return cb(new Error('Routing failed'));
}
});
}
// Leaflet Routing Module
routing = new L.Routing({
position: 'topleft'
,routing: {
router: myRouter
}
,snapping: {
layers: [snapping]
,sensitivity: 15
,vertexonly: false
}
});
map.addControl(routing);
routing.draw(true); // enable drawing mode
$('#eta-export').hide();
$('#eta-export').on('click', function() {
var id = $('#eta-id').val();
if (!id) { alert('Ingen tp_id definert!'); return; }
if (confirm('Eksport til ETA vil overskrive eksisterende geometri!')) {
var coords = routing.toGeoJSON().coordinates;
var data = [];
for (var i = 0; i < coords.length; i++) {
data.push(coords[i][1] + ' ' + coords[i][0]);
}
data = 'LINESTRING(' + data.join(',') + ')';
$.post('http://mintur.ut.no/lib/ajax/post_geom.php?api_key=' + apiKey + '&tp_id=' + id, {coords: data}, function(data) {
if (data.error) {
alert('Eksport feilet med feilkode ' + data.error);
} else if (data.success) {
window.location.href = 'http://mintur.ut.no/index.php?tp_id=' + id + '&tab=kart';
//alert('Eksport suksess!');
}
});
}
});
$('#eta-import').on('click', function() {
var id = $('#eta-id').val();
if (!id) { alert('Ingen tp_id definert!'); return; }
$.get('http://mintur.ut.no/lib/ajax/post_geom.php?api_key=' + apiKey + '&tp_id=' + id, function(data) {
if (data.error) {
alert('Import feilet med feilkode ' + data.error);
} else if (typeof data.coords !== 'undefined') {
$('#eta-import').hide();
$('#eta-export').show();
$('#eta-id').attr('readonly', 'readonly');
if (data.coords) {
data.coords = data.coords.replace('LINESTRING(', '').replace(')', '').split(',');
for (var i = 0; i < data.coords.length; i++) {
data.coords[i] = new L.LatLng(data.coords[i].split(' ')[1], data.coords[i].split(' ')[0]);
}
inport.clearLayers();
var p = new L.Polyline(data.coords, {clickable:false, color: '#000000', opacity: 0.4});
inport.addLayer(p);
map.fitBounds(p.getBounds());
}
}
});
});
function fetchSsrAc(search, cb) {
var result = [];
$.ajax({
url: "https://ws.geonorge.no/SKWS3Index/ssr/sok?navn=" + search + "*&epsgKode=4326&antPerSide=10"
,type: "GET"
,dataType: 'xml'
,success: function(xml) {
$(xml).find('sokRes > stedsnavn').each(function(){
result.push({
title: $(this).find('stedsnavn').text()
,lat: $(this).find('aust').text()
,lng: $(this).find('nord').text()
});
});
cb(null, result);
}
});
}
$('#ssr-search').typeahead({
remote: {
url: 'https://ws.geonorge.no/SKWS3Index/ssr/sok?navn=%QUERY*&epsgKode=4326&antPerSide=10',
dataType: 'xml',
filter: function(xml) {
var result = [];
$(xml).find('sokRes > stedsnavn').each(function(){
result.push({
value: $(this).find('stedsnavn').text()
,tokens: [$(this).find('stedsnavn').text()]
,lat: $(this).find('nord').text()
,lng: $(this).find('aust').text()
});
});
return result;
}
}
});
$('#ssr-search').on('typeahead:selected', function(e, object) {
var ll = new L.LatLng(object.lat, object.lng);
map.panTo(ll);
$('#ssr-search').val('');
})
});
}).call(this);

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

View file

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Routing in Leaflet</title>
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="libs/leaflet/leaflet.ie.css" /><![endif]-->
<link rel="stylesheet" href="app.css" />
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-38558206-1']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</head>
<body>
<div id="search">
<strong>Søk:</strong> <input type="text" id="ssr-search">
</div>
<div id="export">
<strong>tp_id</strong>: <input type="text" id="eta-id">
<button id="eta-export">Eksport</button>
<button id="eta-import">Import</button>
</div>
<div id="map"></div>
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="http://cdn.leafletjs.com/leaflet-0.7/leaflet.js"></script>
<script src="src/utils/LineUtil.Snapping.js"></script>
<script src="src/utils/Marker.Snapping.js"></script>
<script src="src/L.Routing.js"></script>
<script src="src/L.Routing.Storage.js"></script>
<script src="src/L.Routing.Draw.js"></script>
<script src="src/L.Routing.Edit.js"></script>
<script src="http://twitter.github.io/typeahead.js/releases/latest/typeahead.js"></script>
<script src="app.js"></script>
</body>
</html>

View file

@ -0,0 +1,286 @@
/*
* L.Routing.Draw class
*
* Responsible for drawing and contine drawing
*
* @dependencies L, L.Routing
*
* @usage new L.Routing.Draw(map, options);
*/
L.Routing.Draw = L.Handler.extend({
// INCLUDES
includes: [L.Mixin.Events]
// OPTIONS
,options: {}
/**
* Draw Constructor
*
* @access public
*
* @param <> parent - parent class instance
* @param <Oject> options - routing options
*
* @return void
*
* @todo fetch last waypoint
*/
,initialize: function (parent, options) {
this._parent = parent;
this._map = parent._map;
this._enabled = false;
L.Util.setOptions(this, options);
}
/**
* Enable drawing
*
* @access public
*
* @event map.routing:draw-start
* @event map.routing:draw-new
* @event map.routing:draw-continue
*
* @return void
*/
,enable: function() {
if (this._enabled) { return; }
this._enabled = true;
this._hidden = false;
this._dragging = false;
this._addHooks();
this.fire('enabled');
this._map.fire('routing:draw-start');
if (this._parent._segments._layers.length === 0) {
this._map.fire('routing:draw-new');
} else {
this._map.fire('routing:draw-continue');
}
}
/**
* Disable drawing
*
* @access public
*
* @event map.routing:draw-end
*
* @return void
*/
,disable: function() {
if (!this._enabled) { return; }
this._enabled = false;
this._removeHooks();
this.fire('disabled');
this._map.fire('routing:draw-end');
}
/**
* Add hooks
*
* @access private
*
* @return void
*
* @todo hide and style the trailer!
*/
,_addHooks: function() {
if (!this._map) { return; }
// Visible Marker
if (!this._marker) {
this._marker = new L.Marker(this._map.getCenter(), {
icon: this.options.icons.normal
,zIndexOffset: this.options.zIndexOffset
,clickable: false
});
}
// Trailing line
if (!this._trailer) {
var ll = this._map.getCenter();
this._trailer = new L.Polyline([ll, ll], {
opacity: 0.2
,clickable: false
});
}
this._parent.on('waypoint:mouseover', this._catchWaypointEvent, this);
this._parent.on('waypoint:mouseout' , this._catchWaypointEvent, this);
this._parent.on('waypoint:dragstart', this._catchWaypointEvent, this);
this._parent.on('waypoint:dragend' , this._catchWaypointEvent, this);
this._parent.on('segment:mouseover' , this._catchWaypointEvent, this);
this._parent.on('segment:mouseout' , this._catchWaypointEvent, this);
this._parent.on('segment:dragstart' , this._catchWaypointEvent, this);
this._parent.on('segment:dragend' , this._catchWaypointEvent, this);
this._map.on('mousemove', this._onMouseMove, this);
this._map.on('click', this._onMouseClick, this);
this._marker.addTo(this._map);
this._trailer.addTo(this._map);
}
/**
* Remove hooks
*
* This method is invoked after the `disable()` has been called and removes
* all the hooks set up using the `_addHooks()` method.
*
* @access private
*
* @return void
*/
,_removeHooks: function() {
if (!this._map) { return; }
this._parent.off('waypoint:mouseover', this._catchWaypointEvent, this);
this._parent.off('waypoint:mouseout' , this._catchWaypointEvent, this);
this._parent.off('waypoint:dragstart', this._catchWaypointEvent, this);
this._parent.off('waypoint:dragend' , this._catchWaypointEvent, this);
this._parent.off('segment:mouseover' , this._catchWaypointEvent, this);
this._parent.off('segment:mouseout' , this._catchWaypointEvent, this);
this._parent.off('segment:dragstart' , this._catchWaypointEvent, this);
this._parent.off('segment:dragend' , this._catchWaypointEvent, this);
this._map.off('click', this._onMouseClick, this);
this._map.off('mousemove', this._onMouseMove, this);
this._map.removeLayer(this._marker);
this._map.removeLayer(this._trailer);
delete this._marker;
delete this._trailer;
}
/**
* Handle waypoint events
*
* @access private
*
* @param <L.Event> e - waypoint event
*
* @return void
*/
,_catchWaypointEvent: function(e) {
var type = e.type.split(':')[1];
if (this._hidden) {
if (this._dragging) {
if (type === 'dragend') {
this._dragging = false;
}
} else {
if (type === 'mouseout') {
this._show();
} else if (type === 'dragstart') {
this._dragging = true;
}
}
} else {
if (type === 'mouseover') {
this._hide();
}
}
}
/**
* Hide HUD
*
* Call this method in order to quickly hide graphical drawing elements for
* instance hoovering over draggable objects which should tempoarily disable
* dragging.
*
* @access private
*
* @return void
*/
,_hide: function() {
this._hidden = true;
this._marker.setOpacity(0.0);
this._trailer.setStyle({opacity: 0.0});
}
/**
* Show HUD
*
* Call this method to restore graphical drawing elements after they have been
* hidden.
*
* @access private
*
* @return void
*/
,_show: function() {
this._hidden = false;
this._marker.setOpacity(1.0);
this._trailer.setStyle({opacity: 0.2});
}
/**
* Mouse move handler
*
* @access private
*
* @param <L.Event> e - mouse move event
*
* @return void
*/
,_onMouseMove : function(e) {
if (this._hidden) { return; }
var latlng = e.latlng;
var last = this._parent.getLast();
if (this.options.snapping) {
latlng = L.LineUtil.snapToLayers(latlng, null, this.options.snapping);
}
this._marker.setLatLng(latlng);
if (last !== null) {
this._trailer.setLatLngs([last.getLatLng(), latlng]);
};
}
/**
* Mouse click handler
*
* @access private
*
* @param <L.Event> e - mouse click event
*
* @event map.routing:new-waypoint
*
* @return void
*/
,_onMouseClick: function(e) {
if (this._hidden) { return; }
var marker, latlng, last;
latlng = e.latlng;
if (this.options.snapping) {
latlng = L.LineUtil.snapToLayers(latlng, null, this.options.snapping);
}
marker = new L.Marker(latlng);
last = this._parent.getLast();
this._trailer.setLatLngs([latlng, latlng]);
this._parent.addWaypoint(marker, last, null, function(err, data) {
// console.log(err, data);
});
}
});

View file

@ -0,0 +1,387 @@
/*
* L.Routing.Edit class
*
* Responsible handle edits
*
* @dependencies L, L.Routing
*
* @usage new L.Routing.Draw(map, options);
*/
L.Routing.Edit = L.Handler.extend({
// INCLUDES
includes: [L.Mixin.Events]
// OPTIONS
,options: {}
/**
* Edit Constructor
*
* @access public
*
* @param <> parent - parent class instance
* @param <Oject> options - routing options
*
* @return void
*
* @todo fetch last waypoint
*/
,initialize: function (parent, options) {
this._parent = parent;
this._map = parent._map;
this._enabled = false;
L.Util.setOptions(this, options);
}
/**
* Enable drawing
*
* @access public
*
* @event map.routing:edit-start
*
* @return void
*/
,enable: function() {
if (this._enabled) { return; }
this._enabled = true;
this._addHooks();
this.fire('enabled');
this._map.fire('routing:edit-start');
}
/**
* Disable drawing
*
* @access public
*
* @event map.draw:edit-end
*
* @return void
*/
,disable: function() {
if (!this._enabled) { return; }
this._enabled = false;
this._removeHooks();
this.fire('disabled');
this._map.fire('routing:edit-end');
}
/**
* Add hooks
*
* This method is invoked when `enable()` is called and sets up all
* necessary hooks such as:
* * text selection
* * key listeners
* * mouse marker
*
* @access private
*
* @return void
*
* @todo hide and style the trailer!
*/
,_addHooks: function() {
if (!this._map) { return; }
if (!this._mouseMarker) {
this._mouseMarker = L.marker(this._map.getCenter(), {
icon: L.divIcon({
className: 'line-mouse-marker'
,iconAnchor: [5, 5]
,iconSize: [10, 10]
})
,clickable: true
,draggable: true
,opacity: 0
,zIndexOffset: this.options.zIndexOffset
});
}
this._mouseMarker.addTo(this._map);
if (!this._trailer1) {
var ll = this._map.getCenter();
var style = {opacity: 0.0,clickable: false};
this._trailer1 = new L.Polyline([ll, ll], style);
this._trailer2 = new L.Polyline([ll, ll], style);
}
this._trailer1.addTo(this._map);
this._trailer2.addTo(this._map);
this._parent.on('segment:mouseover' , this._segmentOnMouseover, this);
this._mouseMarker.on('dragstart' , this._segmentOnDragstart, this);
this._mouseMarker.on('drag' , this._segmentOnDrag, this);
this._mouseMarker.on('dragend' , this._segmentOnDragend, this);
this._parent.on('waypoint:dragstart', this._waypointOnDragstart, this);
this._parent.on('waypoint:drag' , this._waypointOnDrag, this);
this._parent.on('waypoint:dragend' , this._waypointOnDragend, this);
}
/**
* Remove hooks
*
* This method is invoked after the `disable()` has been called and removes
* all the hooks set up using the `_addHooks()` method.
*
* @access private
*
* @return void
*/
,_removeHooks: function() {
if (!this._map) { return; }
// this._trailer1.addTo(this._map);
// this._trailer2.addTo(this._map);
this._parent.off('segment:mouseover' , this._segmentOnMouseover, this);
this._mouseMarker.off('dragstart' , this._segmentOnDragstart, this);
this._mouseMarker.off('drag' , this._segmentOnDrag, this);
this._mouseMarker.off('dragend' , this._segmentOnDragend, this);
this._parent.off('waypoint:dragstart', this._waypointOnDragstart, this);
this._parent.off('waypoint:drag' , this._waypointOnDrag, this);
this._parent.off('waypoint:dragend' , this._waypointOnDragend, this);
}
/**
* Fired when the mouse first enters a segment
*
* @access private
*
* @param <L.Event> e - mouse over event
*
* @return void
*/
,_segmentOnMouseover: function(e) {
this._mouseMarker.setOpacity(1.0);
this._map.on('mousemove', this._segmentOnMousemove, this);
}
/**
* Fired when the mouse leaves a segement
*
* @access private
*
* @param <L.Event> e - mouse move event
*
* @return void
*/
,_segmentOnMouseout: function(e) {
if (this._dragging) { return; }
this._mouseMarker.setOpacity(0.0);
this._map.off('mousemove', this._segmentOnMousemove, this);
this.fire('segment:mouseout');
}
/**
* Fired when the mouse is moved
*
* This method is fired continously when mouse is moved in edition mode.
*
* @access private
*
* @param <L.Event> e - mouse move event
*
* @return void
*/
,_segmentOnMousemove: function(e) {
if (this._dragging) { return; }
var latlng = L.LineUtil.snapToLayers(e.latlng, null, {
layers: [this._parent._segments]
,sensitivity: 40
,vertexonly: false
});
if (latlng._feature === null) {
this._segmentOnMouseout(e);
} else {
this._mouseMarker._snapping = latlng._feature._routing;
this._mouseMarker.setLatLng(latlng);
}
}
/**
* Mouse marker dragstart
*
* @access private
*
* @param <L.Event> e - mouse dragstart event
*
* @return void
*/
,_segmentOnDragstart: function(e) {
var latlng = e.target.getLatLng();
var next = e.target._snapping.nextMarker;
var prev = e.target._snapping.prevMarker;
this._setTrailers(latlng, next, prev, true);
this._dragging = true;
this.fire('segment:dragstart');
}
/**
* Fired when a marker is dragged
*
* This method is fired continously when dragging a marker and snapps the
* marker to the snapping layer.
*
* @access private
*
* @param <L.Event> e - mouse drag event
*
* @return void
*/
,_segmentOnDrag: function(e) {
var latlng = e.target.getLatLng();
var next = e.target._snapping.nextMarker;
var prev = e.target._snapping.prevMarker;
if (this.options.snapping) {
latlng = L.LineUtil.snapToLayers(latlng, null, this.options.snapping);
}
e.target.setLatLng(latlng);
this._setTrailers(latlng, next, prev);
}
/**
* Mouse marker dragend
*
* @access private
*
* @param <L.Event> e - mouse dragend event
*
* @return void
*/
,_segmentOnDragend: function(e) {
var next = this._mouseMarker._snapping.nextMarker;
var prev = this._mouseMarker._snapping.prevMarker;
var latlng = this._mouseMarker.getLatLng();
this._parent.addWaypoint(latlng, prev, next, function(err, data) {
//console.log(err, data);
});
this._dragging = false;
this._setTrailers(null, null, null, false);
this.fire('segment:dragend');
}
/**
* Fired when marker drag start
*
* @access private
*
* @param <L.Event> e - mouse dragend event
*
* @return void
*/
,_waypointOnDragstart: function(e) {
var next = e.marker._routing.nextMarker;
var prev = e.marker._routing.prevMarker;
this._setTrailers(e.marker.getLatLng(), next, prev, true);
}
/**
* Fired while dragging marker
*
* @access private
*
* @access private
*
* @param <L.Event> e - mouse drag event
*
* @return void
*/
,_waypointOnDrag: function(e) {
var latlng = e.marker._latlng;
var next = e.marker._routing.nextMarker;
var prev = e.marker._routing.prevMarker;
if (this.options.snapping) {
latlng = L.LineUtil.snapToLayers(latlng, null, this.options.snapping);
}
e.marker.setLatLng(latlng);
this._setTrailers(latlng, next, prev);
}
/**
* Fired when marker drag ends
*
* @access private
*
* @param <L.Event> e - mouse dragend event
*
* @return void
*/
,_waypointOnDragend: function(e) {
this._setTrailers(null, null, null, false);
this._parent.routeWaypoint(e.marker, function(err, data) {
//console.log('_waypointOnDragend.cb', err, data);
});
}
/**
* Fired when marker is clicked
*
* This method is fired when a marker is clicked by the user. It will then
* procede to remove the marker and reroute any connected line segments.
*
* @access private
*
* @param <L.Event> e - mouse click event
*
* @return void
*/
,_waypointOnClick: function(e) {
this._parent.removeWaypoint(e.layer, function(err, data) {
//console.log('_waypointOnDragend.cb', err, data);
});
}
/**
* Set trailing guide lines
*
*/
,_setTrailers: function(latlng, next, prev, show) {
if (typeof show !== 'undefined') {
if (show === false) {
this._trailer1.setStyle({opacity: 0.0});
this._trailer2.setStyle({opacity: 0.0});
return;
} else {
if (next !== null) {
this._trailer1.setStyle({opacity: 0.2});
}
if (prev !== null) {
this._trailer2.setStyle({opacity: 0.2});
}
}
}
if (next) {
this._trailer1.setLatLngs([latlng, next.getLatLng()]);
}
if (prev) {
this._trailer2.setLatLngs([latlng, prev.getLatLng()]);
}
}
});

View file

@ -0,0 +1,31 @@
/*
* Leaflet Routing Storage
*
* Storing routable objects
*
* @dependencies L, L.Routing
*
* @usage new L.Routing(options);
*/
(function () {
L.Routing.Storage = L.MultiPolyline.extend({
/**
* Class constructor
*/
initialize: function (latlngs, options) {
this._layers = {};
this._options = options;
this.setLatLngs(latlngs);
this.on('layeradd', function() {
console.log('layeradd', arguments);
}, this);
}
});
L.Routing.storage = function (latlngs, options) {
return new L.MultiPolyline(latlngs, options);
};
}());

View file

@ -0,0 +1,556 @@
/*
* L.Routing main class
*
* Main clase for the Leaflet routing module
*
* @dependencies L
*
* @usage new L.Routing(options);
*
* @todo use L.Class.extend instead?
*/
L.Routing = L.Control.extend({
// INCLUDES
includes: [L.Mixin.Events]
// CONSTANTS
,statics: {
VERSION: '0.0.2-dev'
}
// OPTIONS
,options: {
position: 'topleft'
,icons: {
start: new L.Icon.Default()
,end: new L.Icon.Default()
,normal: new L.Icon.Default()
}
,zIndexOffset: 2000
,routing: {
router: null // function (<L.Latlng> l1, <L.Latlng> l2, <Function> cb)
}
,snapping: {
layers: [] // layers to snap to
,sensitivity: 10 // snapping sensitivity
,vertexonly: false // vertex only snapping
}
}
/**
* Routing Constructor
*
* @access public
*
* @param <Object> options - non-default options
*
* @todo render display of segments and waypoints
*/
,initialize: function (options) {
this._editing = false;
this._drawing = false;
L.Util.setOptions(this, options);
}
/**
* Called when controller is added to map
*
* @access public
*
* @param <L.Map> map - map instance
*
* @return <HTMLElement> container
*/
,onAdd: function (map) {
this._map = map;
this._container = this._map._container;
this._overlayPane = this._map._panes.overlayPane;
this._popupPane = this._map._panes.popupPane;
this._router = this.options.routing.router;
this._segments = new L.FeatureGroup().addTo(map);
this._waypoints = new L.FeatureGroup().addTo(map);
this._waypoints._first = null;
this._waypoints._last = null;
//L.DomUtil.disableTextSelection();
//this._tooltip = new L.Tooltip(this._map);
//this._tooltip.updateContent({ text: L.drawLocal.draw.marker.tooltip.start });
L.DomEvent.addListener(this._container, 'keyup', this._keyupListener, this);
this._draw = new L.Routing.Draw(this, {
icons: this.options.icons
,zIndexOffset: this.options.zIndexOffset
,snapping: this.options.snapping
});
this._edit = new L.Routing.Edit(this, {
icons: this.options.icons
,zIndexOffset: this.options.zIndexOffset
,snapping: this.options.snapping
});
this._edit.enable();
this.on('waypoint:click', this._waypointClickHandler, this)
this._segments.on('mouseover' , this._fireSegmentEvent, this);
this._edit.on('segment:mouseout' , this._fireSegmentEvent, this);
this._edit.on('segment:dragstart', this._fireSegmentEvent, this);
this._edit.on('segment:dragend' , this._fireSegmentEvent, this);
var container = L.DomUtil.create('div', 'leaflet-routing');
return container;
}
/**
* Called when controller is removed from map
*
* @access public
*
* @param <L.Map> map - map instance
*/
,onRemove: function(map) {
//L.DomUtil.create('div', 'leaflet-routing'); <= delete this
this.off('waypoint:click', this._waypointClickHandler, this)
this._segments.off('mouseover' , this._fireSegmentEvent, this);
this._edit.off('segment:mouseout' , this._fireSegmentEvent, this);
this._edit.off('segment:dragstart', this._fireSegmentEvent, this);
this._edit.off('segment:dragend' , this._fireSegmentEvent, this);
this._edit.disable();
this._draw.disable();
L.DomUtil.enableTextSelection();
// this._tooltip.dispose();
// this._tooltip = null;
L.DomEvent.removeListener(this._container, 'keyup', this._keyupListener);
delete this._draw;
delete this._edit;
delete this._map;
delete this._router;
delete this._segments;
delete this._waypoints;
delete this.options;
}
,_waypointClickHandler: function(e) {
this.removeWaypoint(e.marker, function() {
console.log(arguments);
});
}
/**
* Add new waypoint to path
*
* @access public
*
* @param <L.Marker> marker - new waypoint marker (can be ll)
* @param <L.Marker> prev - previous waypoint marker
* @param <L.Marker> next - next waypoint marker
* @param <Function> cb - callback method
*
* @return void
*/
,addWaypoint: function(marker, prev, next, cb) {
if (marker instanceof L.LatLng) {
marker = new L.Marker(marker);
}
marker._routing = {
prevMarker : prev
,nextMarker : next
,prevLine : null
,nextLine : null
,timeoutID : null
};
if (this._waypoints._first === null && this._waypoints._last === null) {
this._waypoints._first = marker;
this._waypoints._last = marker;
} else if (next === null) {
this._waypoints._last = marker;
} else if (prev === null) {
this._waypoints._first = marker;
}
if (marker._routing.prevMarker !== null) {
marker._routing.prevMarker._routing.nextMarker = marker;
marker._routing.prevLine = marker._routing.prevMarker._routing.nextLine;
if (marker._routing.prevLine !== null) {
marker._routing.prevLine._routing.nextMarker = marker;
}
}
if (marker._routing.nextMarker !== null) {
marker._routing.nextMarker._routing.prevMarker = marker;
marker.nextLine = marker._routing.nextMarker._routing.prevLine;
if (marker._routing.nextLine !== null) {
marker._routing.nextLine._routing.prevMarker = marker;
}
}
marker.on('mouseover', this._fireWaypointEvent, this);
marker.on('mouseout' , this._fireWaypointEvent, this);
marker.on('dragstart', this._fireWaypointEvent, this);
marker.on('dragend' , this._fireWaypointEvent, this);
marker.on('drag' , this._fireWaypointEvent, this);
marker.on('click' , this._fireWaypointEvent, this);
this.routeWaypoint(marker, cb);
this._waypoints.addLayer(marker);
marker.dragging.enable();
}
/**
* Remove a waypoint from path
*
* @access public
*
* @param <L.Marker> marker - new waypoint marker (can be ll)
* @param <Function> cb - callback method
*
* @return void
*/
,removeWaypoint: function(marker, cb) {
marker.off('mouseover', this._fireWaypointEvent, this);
marker.off('mouseout' , this._fireWaypointEvent, this);
marker.off('dragstart', this._fireWaypointEvent, this);
marker.off('dragend' , this._fireWaypointEvent, this);
marker.off('drag' , this._fireWaypointEvent, this);
marker.off('click' , this._fireWaypointEvent, this);
var prev = marker._routing.prevMarker;
var next = marker._routing.nextMarker;
if (this._waypoints._first && marker._leaflet_id === this._waypoints._first._leaflet_id) {
this._waypoints._first = next;
}
if (this._waypoints._last && marker._leaflet_id === this._waypoints._last._leaflet_id) {
this._waypoints._last = prev;
}
if (prev !== null) {
prev._routing.nextMarker = next;
prev._routing.nextLine = null;
}
if (next !== null) {
next._routing.prevMarker = prev;
next._routing.prevLine = null;
}
if (marker._routing.nextLine !== null) {
this._segments.removeLayer(marker._routing.nextLine);
}
if (marker._routing.prevLine !== null) {
this._segments.removeLayer(marker._routing.prevLine);
}
this._waypoints.removeLayer(marker);
if (prev !== null) {
this.routeWaypoint(prev, cb);
} else if (next !== null) {
this.routeWaypoint(next, cb);
} else {
this._draw.enable();
cb(null, null);
}
}
/**
* Route with respect to waypoint
*
* @access public
*
* @param <L.Marker> marker - marker to route on
* @param <Function> cb - callback function
*
* @return void
*
* @todo add propper error checking for callback
*/
,routeWaypoint: function(marker, cb) {
var i = 0;
var $this = this;
var callback = function(err, data) {
i++;
if (i === 2) {
$this.fire('routing:routeWaypointEnd');
cb(err, marker);
}
}
this.fire('routing:routeWaypointStart');
this._routeSegment(marker._routing.prevMarker, marker, callback);
this._routeSegment(marker, marker._routing.nextMarker, callback);
}
/**
* Route segment between two markers
*
* @access private
*
* @param <L.Marker> m1 - first waypoint marker
* @param <L.Marker> m2 - second waypoint marker
* @param <Function> cb - callback function (<Error> err, <String> data)
*
* @return void
*
* @todo logic if router fails
*/
,_routeSegment: function(m1, m2, cb) {
var $this = this;
if (m1 === null || m2 === null) {
return cb(null, true);
}
this._router(m1.getLatLng(), m2.getLatLng(), function(err, layer) {
if (typeof layer === 'undefined') {
var layer = new L.Polyline([m1.getLatLng(), m2.getLatLng()]);
}
layer._routing = {
prevMarker: m1
,nextMarker: m2
};
if (m1._routing.nextLine !== null) {
$this._segments.removeLayer(m1._routing.nextLine);
}
$this._segments.addLayer(layer);
m1._routing.nextLine = layer;
m2._routing.prevLine = layer;
return cb(null, layer);
});
}
/**
* Iterate over all segments and execute callback for each segment
*
* @access private
*
* @param <function> callback - function to call for each segment
* @param <object> context - callback execution context (this). Optional, default: this
*
* @return void
*/
,_eachSegment: function(callback, context) {
var thisArg = context || this;
var marker = this.getFirst();
if (marker === null) { return; }
while (marker._routing.nextMarker !== null) {
var m1 = marker;
var m2 = marker._routing.nextMarker;
var line = marker._routing.nextLine;
callback.call(thisArg, m1, m2, line);
marker = marker._routing.nextMarker;
}
}
/**
* Fire events
*
* @access private
*
* @param <L.Event> e - mouse event
*
* @return void
*/
,_fireWaypointEvent: function(e) {
this.fire('waypoint:' + e.type, {marker:e.target});
}
/**
*
*/
,_fireSegmentEvent: function(e) {
if (e.type.split(':').length === 2) {
this.fire(e.type);
} else {
this.fire('segment:' + e.type);
}
}
/**
* Get first waypoint
*
* @access public
*
* @return L.Marker
*/
,getFirst: function() {
return this._waypoints._first;
}
/**
* Get last waypoint
*
* @access public
*
* @return L.Marker
*/
,getLast: function() {
return this._waypoints._last;
}
/**
* Get all waypoints
*
* @access public
*
* @return <L.LatLng[]> all waypoints or empty array if none
*/
,getWaypoints: function() {
var latLngs = [];
this._eachSegment(function(m1) {
latLngs.push(m1.getLatLng());
});
if (this.getLast()) {
latLngs.push(this.getLast().getLatLng());
}
return latLngs;
}
/**
* Concatenates all route segments to a single polyline
*
* @access public
*
* @return <L.Polyline> polyline, with empty _latlngs when no route segments
*/
,toPolyline: function() {
var latLngs = [];
this._eachSegment(function(m1, m2, line) {
latLngs = latLngs.concat(line.getLatLngs());
});
return L.polyline(latLngs);
}
/**
* Export route to GeoJSON
*
* @access public
*
* @param <boolean> enforce2d - enforce 2DGeoJSON
*
* @return <object> GeoJSON object
*
*/
,toGeoJSON: function(enforce2d) {
var geojson = {type: "LineString", properties: {waypoints: []}, coordinates: []};
var current = this._waypoints._first;
if (current === null) { return geojson; }
geojson.properties.waypoints.push([current.getLatLng().lng, current.getLatLng().lat]);
while (current._routing.nextMarker) {
var next = current._routing.nextMarker
geojson.properties.waypoints.push([next.getLatLng().lng, next.getLatLng().lat]);
var tmp = current._routing.nextLine.getLatLngs();
for (var i = 0; i < tmp.length; i++) {
if (tmp[i].alt && (typeof enforce2d === 'undefined' || enforce2d === false)) {
geojson.coordinates.push([tmp[i].lat, tmp[i].lng, tmp[i].alt]);
} else {
geojson.coordinates.push([tmp[i].lat, tmp[i].lng]);
}
}
current = current._routing.nextMarker;
}
return geojson
}
/**
* Start (or continue) drawing
*
* Call this method in order to start or continue drawing. The drawing handler
* will be activate and the user can draw on the map.
*
* @access public
*
* @return void
*
* @todo check enable
*/
,draw: function (enable) {
if (typeof enable === 'undefined') {
var enable = true;
}
if (enable) {
this._draw.enable();
} else {
this._draw.disable();
}
}
/**
* Enable or disable routing
*
* @access public
*
* @return void
*
* @todo check enable
*/
,routing: function (enable) {
throw new Error('Not implemented');
}
/**
* Enable or disable snapping
*
* @access public
*
* @return void
*
* @todo check enable
*/
,snapping: function (enable) {
throw new Error('Not implemented');
}
/**
* Key up listener
*
* * `ESC` to cancel drawing
* * `M` to enable drawing
*
* @access private
*
* @return void
*/
,_keyupListener: function (e) {
if (e.keyCode === 27) {
this._draw.disable();
} else if (e.keyCode === 77) {
this._draw.enable();
}
}
});

View file

@ -0,0 +1,186 @@
L.Util.extend(L.LineUtil, {
/**
* Snap to all layers
*
* @param <Latlng> latlng - original position
* @param <Number> id - leaflet unique id
* @param <Object> opts - snapping options
*
* @return <Latlng> closest point
*/
snapToLayers: function (latlng, id, opts) {
var i, j, keys, feature, res, sensitivity, vertexonly, layers, minDist, minPoint, map;
sensitivity = opts.sensitivity || 10;
vertexonly = opts.vertexonly || false;
layers = opts.layers || [];
minDist = Infinity;
minPoint = latlng;
minPoint._feature = null; // containing layer
map = opts.layers[0]._map; // @todo check for undef
for (i = 0; i < opts.layers.length; i++) {
keys = Object.keys(opts.layers[i]._layers);
for (j = 0; j < keys.length; j++) {
feature = opts.layers[i]._layers[keys[j]];
// Don't even try snapping to itself!
if (id === feature._leaflet_id) { continue; }
// GeometryCollection
if (feature._layers) {
var newLatlng = this.snapToLayers(latlng, id, {
'sensitivity': sensitivity,
'vertexonly': vertexonly,
'layers': [feature]
});
// What if this is the same?
res = {'minDist': latlng.distanceTo(newLatlng), 'minPoint': newLatlng};
// Marker
} else if (feature instanceof L.Marker) {
res = this._snapToLatlngs(latlng, [feature.getLatLng()], map, sensitivity, vertexonly, minDist);
// Polyline
} else if (feature instanceof L.Polyline) {
res = this._snapToLatlngs(latlng, feature.getLatLngs(), map, sensitivity, vertexonly, minDist);
// MultiPolyline
} else if (feature instanceof L.MultiPolyline) {
console.error('Snapping to MultiPolyline is currently unsupported', feature);
res = {'minDist': minDist, 'minPoint': minPoint};
// Polygon
} else if (feature instanceof L.Polygon) {
res = this._snapToPolygon(latlng, feature, map, sensitivity, vertexonly, minDist);
// MultiPolygon
} else if (feature instanceof L.MultiPolygon) {
res = this._snapToMultiPolygon(latlng, feature, map, sensitivity, vertexonly, minDist);
// Unknown
} else {
console.error('Unsupported snapping feature', feature);
res = {'minDist': minDist, 'minPoint': minPoint};
}
if (res.minDist < minDist) {
minDist = res.minDist;
minPoint = res.minPoint;
minPoint._feature = feature;
}
}
}
return minPoint;
},
/**
* Snap to Polygon
*
* @param <Latlng> latlng - original position
* @param <L.Polygon> feature -
* @param <L.Map> map -
* @param <Number> sensitivity -
* @param <Boolean> vertexonly -
* @param <Number> minDist -
*
* @return <Object> minDist and minPoint
*/
_snapToPolygon: function (latlng, polygon, map, sensitivity, vertexonly, minDist) {
var res, keys, latlngs, i, minPoint;
minPoint = null;
latlngs = polygon.getLatLngs();
latlngs.push(latlngs[0]);
res = this._snapToLatlngs(latlng, polygon.getLatLngs(), map, sensitivity, vertexonly, minDist);
if (res.minDist < minDist) {
minDist = res.minDist;
minPoint = res.minPoint;
}
keys = Object.keys(polygon._holes);
for (i = 0; i < keys.length; i++) {
latlngs = polygon._holes[keys[i]];
latlngs.push(latlngs[0]);
res = this._snapToLatlngs(latlng, polygon._holes[keys[i]], map, sensitivity, vertexonly, minDist);
if (res.minDist < minDist) {
minDist = res.minDist;
minPoint = res.minPoint;
}
}
return {'minDist': minDist, 'minPoint': minPoint};
},
/**
* Snap to MultiPolygon
*
* @param <Latlng> latlng - original position
* @param <L.Polygon> feature -
* @param <L.Map> map -
* @param <Number> sensitivity -
* @param <Boolean> vertexonly -
* @param <Number> minDist -
*
* @return <Object> minDist and minPoint
*/
_snapToMultiPolygon: function (latlng, multipolygon, map, sensitivity, vertexonly, minDist) {
var i, keys, res, minPoint;
minPoint = null;
keys = Object.keys(multipolygon._layers);
for (i = 0; i < keys.length; i++) {
res = this._snapToPolygon(latlng, multipolygon._layers[keys[i]], map, sensitivity, vertexonly, minDist);
if (res.minDist < minDist) {
minDist = res.minDist;
minPoint = res.minPoint;
}
}
return {'minDist': minDist, 'minPoint': minPoint};
},
/**
* Snap to <Array> of <Latlang>
*
* @param <LatLng> latlng - cursor click
* @param <Array> latlngs - array of <L.LatLngs> to snap to
* @param <Object> opts - snapping options
* @param <Boolean> isPolygon - if feature is a polygon
*
* @return <Object> minDist and minPoint
*/
_snapToLatlngs: function (latlng, latlngs, map, sensitivity, vertexonly, minDist) {
var i, tmpDist, minPoint, p, p1, p2, d2;
p = map.latLngToLayerPoint(latlng);
p1 = minPoint = null;
for (i = 0; i < latlngs.length; i++) {
p2 = map.latLngToLayerPoint(latlngs[i]);
if (!vertexonly && p1 !== null) {
tmpDist = L.LineUtil.pointToSegmentDistance(p, p1, p2);
if (tmpDist < minDist && tmpDist <= sensitivity) {
minDist = tmpDist;
minPoint = map.layerPointToLatLng(L.LineUtil.closestPointOnSegment(p, p1, p2));
}
} else if ((d2 = p.distanceTo(p2)) && d2 <= sensitivity && d2 < minDist) {
minDist = d2;
minPoint = latlngs[i];
}
p1 = p2;
}
return {'minDist': minDist, 'minPoint': minPoint};
}
});

View file

@ -0,0 +1,12 @@
L.Marker.include({
/**
* Snap to function
*
* @param <LatLng> latlng - original position
*
* @return <LatLng> - new position
*/
snapTo: function (latlng) {
return L.LineUtil.snapToLayers(latlng, this._leaflet_id, this.options.snapping);
}
});

View file

@ -0,0 +1,12 @@
L.Polyline.include({
/**
* Snap to function
*
* @param <LatLng> latlng - original position
*
* @return <LatLng> - new position
*/
snapTo: function (latlng) {
return L.LineUtil.snapToLayers(latlng, this._leaflet_id, this.options.snapping);
}
});