Last active
June 11, 2016 11:59
-
-
Save helderdarocha/7174b89572f075d5f8387a7f2275135f to your computer and use it in GitHub Desktop.
Leaflet.draw and path simplification algorithm
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
height: 800 | |
license: cc-by-sa-4.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<title>Title</title> | |
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet/v0.7.7/leaflet.css" /> | |
<link rel="stylesheet" href="https://leaflet.github.io/Leaflet.draw/leaflet.draw.css" /> | |
<script src="http://cdn.leafletjs.com/leaflet/v0.7.7/leaflet.js"></script> | |
<script src="https://code.jquery.com/jquery-3.0.0.min.js" integrity="sha256-JmvOoLtYsmqlsWxa7mDSLMwa6dZ9rrIdtrrVYRnDRH0=" crossorigin="anonymous"></script> | |
<script src="https://leaflet.github.io/Leaflet.draw/leaflet.draw.js"></script> | |
<style> | |
#map { | |
height: 720px; | |
border: solid 1px red; | |
} | |
</style> | |
<script> | |
// PATH SIMPLIFICATION ALGORITHM | |
// Douglas Peucker path simplification algorithm | |
// This code is based on https://gist.github.com/adammiller/826148 | |
var Line = function( p1, p2 ) { | |
this.p1 = p1; | |
this.p2 = p2; | |
this.distanceToPoint = function( point ) { | |
// slope | |
var m = ( this.p2[0] - this.p1[0] ) / ( this.p2[1] - this.p1[1] ), | |
// y offset | |
b = this.p1[0] - ( m * this.p1[1] ), | |
d = []; | |
// distance to the linear equation | |
d.push( Math.abs( point[0] - ( m * point[1] ) - b ) / Math.sqrt( Math.pow( m, 2 ) + 1 ) ); | |
// distance to p1 | |
d.push( Math.sqrt( Math.pow( ( point[1] - this.p1[1] ), 2 ) + Math.pow( ( point[0] - this.p1[0] ), 2 ) ) ); | |
// distance to p2 | |
d.push( Math.sqrt( Math.pow( ( point[1] - this.p2[1] ), 2 ) + Math.pow( ( point[0] - this.p2[0] ), 2 ) ) ); | |
// return the smallest distance | |
return d.sort( function( a, b ) { | |
return ( a - b ); //causes an array to be sorted numerically and ascending | |
} )[0]; | |
}; | |
}; | |
var simplify = function(points, tolerance) { | |
var douglasPeucker = function( points, tolerance ) { | |
if ( points.length <= 2 ) { | |
return [points[0]]; | |
} | |
var returnPoints = [], | |
// make line from start to end | |
line = new Line( points[0], points[points.length - 1] ), | |
// find the largest distance from intermediate poitns to this line | |
maxDistance = 0, | |
maxDistanceIndex = 0, | |
p; | |
for( var i = 1; i <= points.length - 2; i++ ) { | |
var distance = line.distanceToPoint( points[ i ] ); | |
if( distance > maxDistance ) { | |
maxDistance = distance; | |
maxDistanceIndex = i; | |
} | |
} | |
// check if the max distance is greater than our tollerance allows | |
if ( maxDistance >= tolerance ) { | |
p = points[maxDistanceIndex]; | |
line.distanceToPoint( p, true ); | |
// include this point in the output | |
returnPoints = returnPoints.concat( douglasPeucker( points.slice( 0, maxDistanceIndex + 1 ), tolerance ) ); | |
// returnPoints.push( points[maxDistanceIndex] ); | |
returnPoints = returnPoints.concat( douglasPeucker( points.slice( maxDistanceIndex, points.length ), tolerance ) ); | |
} else { | |
// ditching this point | |
p = points[maxDistanceIndex]; | |
line.distanceToPoint( p, true ); | |
returnPoints = [points[0]]; | |
} | |
return returnPoints; | |
}; | |
var result = douglasPeucker( points, tolerance ); | |
// always have to push the very last point on so it doesn't get left off | |
result.push( points[points.length - 1 ] ); | |
return result; | |
}; | |
</script> | |
</head> | |
<body> | |
<div id="map"></div> | |
<form> | |
Simplify path | |
<input type="range" id="tolerance" name="tolerance" value="0" min="0.0000000001" max=".005" step=".0000001"> | |
<button type="button" id="btn_save">Save to database</button> | |
<button type="button" id="btn_load">Load from database</button> | |
</form> | |
<script> | |
//var MAPBOX_ACCESS_TOKEN = "PLACE_YOUR_TOKEN_HERE"; | |
// GLOBAL VARS | |
var mapCenter = [-23.5400239, -46.6793203]; // for this example | |
// original points (ajax load this from a file or database) | |
var original = []; | |
// load the map and original track | |
var points = cloneCoords(original); // this will locally store points after editing and simplification | |
var simplified = cloneCoords(original); // this will locally store points during simplification | |
var track; // this will be the L.polyline containing the points | |
$("#tolerance").prop("disabled", true); | |
console.log("original points length", points.length) | |
// MAP SETUP AND CONFIGURATION | |
var map = L.map('map') | |
.setView(mapCenter, 15); | |
//L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token={accessToken}', { | |
L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { | |
attribution: 'Map data © <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="http://mapbox.com">Mapbox</a>', | |
maxZoom: 18, | |
//id: 'mapbox.streets', | |
//accessToken: MAPBOX_ACCESS_TOKEN | |
}).addTo(map); | |
// Add the editable layer | |
var trackLayer = new L.FeatureGroup(); | |
map.addLayer(trackLayer); | |
// Setup editing controls | |
var drawControl = new L.Control.Draw({ | |
edit: {featureGroup: trackLayer, remove: false}, | |
draw: false | |
}); | |
map.addControl(drawControl); | |
// Editing events | |
map.on('draw:editstart', function (e) { | |
console.log("started editing") | |
points = cloneCoords(simplified); | |
$("#tolerance").prop("disabled", true); | |
}); | |
map.on('draw:edited', function (e) { | |
console.log("finished editing") | |
var editedPoints = []; | |
var coords = track._latlngs; | |
for (var i = 0; i < coords.length; i++) { | |
var obj = coords[i]; | |
editedPoints.push([obj.lat, obj.lng]); | |
} | |
console.log("edited points length", editedPoints.length) | |
points = cloneCoords(editedPoints); | |
}); | |
map.on('draw:editstop', function (e) { | |
console.log("saved or cancelled") | |
$("#tolerance").prop("disabled", false); | |
}); | |
// FUNCTIONS | |
function cloneCoords(sourceArray) { | |
var destArray = []; | |
for(var i = 0; i < sourceArray.length; i++) { | |
destArray.push([sourceArray[i][0],sourceArray[i][1]]); | |
} | |
return destArray; | |
} | |
// Apply simplification algorithm using specified tolerance & redraw shape | |
// Save points in memory | |
function simplifyPath() { | |
var tolerance = $('#tolerance').val(); | |
simplified = simplify(points, tolerance); | |
console.log("simplified points length", simplified.length) | |
// change the array for the path & redraw | |
track.setLatLngs(simplified); | |
} | |
// Revert shape to original points | |
function loadFromDatabase() { | |
if(points.length == 0 || confirm("Reset all changes and revert to original path?")) { | |
// mock data - load points from database into this variable | |
original = [[-23.5399944,-46.6779631],[-23.5399501,-46.6778719],[-23.5398518,-46.6778183],[-23.5397633,-46.6777754],[-23.5396895,-46.6777164],[-23.5396206,-46.6776359],[-23.5395272,-46.6775984],[-23.5394436,-46.6775501],[-23.53936,-46.6775018],[-23.5392665,-46.677416],[-23.5391633,-46.6773355],[-23.5390649,-46.6772443],[-23.5389616,-46.6771853],[-23.5388682,-46.6771102],[-23.53876,-46.6770566],[-23.5388239,-46.6768849],[-23.5389075,-46.6767454],[-23.5390206,-46.6766596],[-23.53906,-46.6765684],[-23.5391288,-46.6764665],[-23.5391878,-46.6763484],[-23.5392616,-46.6762787],[-23.5393108,-46.6761822],[-23.5393796,-46.6761178],[-23.5395567,-46.6759408],[-23.5396354,-46.6757476],[-23.5397436,-46.6755652],[-23.5398419,-46.675458],[-23.5399501,-46.6755223],[-23.5401173,-46.6755652],[-23.5402649,-46.6756618],[-23.5403731,-46.6757047],[-23.5405206,-46.6757905],[-23.5406682,-46.6758764],[-23.5407665,-46.67593],[-23.5408747,-46.6760159],[-23.5409927,-46.6760373],[-23.5411993,-46.6761017],[-23.5412977,-46.6761553],[-23.5414157,-46.6762412],[-23.5417993,-46.6763484],[-23.542055,-46.6763592],[-23.5422419,-46.6764128],[-23.542537,-46.6764557],[-23.5427534,-46.6764665],[-23.5430091,-46.6765201],[-23.5432255,-46.6764987],[-23.5433927,-46.6765201],[-23.5436583,-46.6765523],[-23.5438353,-46.6765416],[-23.5440222,-46.6765523],[-23.5442091,-46.6765416],[-23.5443664,-46.6765738],[-23.5446713,-46.6766381],[-23.5448287,-46.6766381],[-23.5452025,-46.6766703],[-23.5455074,-46.6766918],[-23.5459696,-46.676681],[-23.5460778,-46.676724],[-23.5463237,-46.6767669],[-23.5465303,-46.6767883],[-23.5465991,-46.6767991],[-23.5469925,-46.6767883],[-23.5471794,-46.6767991],[-23.5473466,-46.6768527],[-23.5476122,-46.6768527],[-23.547799,-46.6768956],[-23.5481039,-46.67696],[-23.5482515,-46.67696],[-23.548399,-46.6769707],[-23.5485662,-46.6769707],[-23.5487629,-46.6769922],[-23.5489596,-46.6770136],[-23.5491661,-46.6770673],[-23.549471,-46.6770673],[-23.5496186,-46.6771102],[-23.5498153,-46.6771638],[-23.550012,-46.6772175],[-23.5501595,-46.6772604],[-23.5503857,-46.6773462],[-23.5506414,-46.6774213],[-23.5508283,-46.6774642],[-23.5509955,-46.6775501],[-23.5513102,-46.6777325],[-23.5515266,-46.677947],[-23.5517528,-46.6781294],[-23.5519889,-46.6783011],[-23.5521757,-46.6784835],[-23.5523429,-46.6787302],[-23.5524118,-46.6788912],[-23.5521659,-46.679374],[-23.5520479,-46.679492],[-23.55192,-46.6795886],[-23.551743,-46.6796422],[-23.5516348,-46.6797066],[-23.5515069,-46.679846],[-23.5513397,-46.680007],[-23.551143,-46.6800606],[-23.5513397,-46.6798675],[-23.5515758,-46.6800714],[-23.5515954,-46.6799641],[-23.5513594,-46.679728],[-23.5512611,-46.6794813],[-23.5511332,-46.6792989],[-23.5510447,-46.6790736],[-23.5509365,-46.6789234],[-23.5508283,-46.6788161],[-23.5508578,-46.6786659],[-23.5510348,-46.67858],[-23.551143,-46.6785371],[-23.5512512,-46.6784728],[-23.5514479,-46.6783333],[-23.5516348,-46.6782796],[-23.5519003,-46.6780221],[-23.5519889,-46.6779149],[-23.5521856,-46.6778183],[-23.5523429,-46.6776788],[-23.5524609,-46.677593],[-23.552579,-46.6774642],[-23.5525298,-46.6773462],[-23.5524118,-46.6770995],[-23.5522839,-46.6769063],[-23.5521659,-46.6767669],[-23.552038,-46.6765523],[-23.5517036,-46.6764772],[-23.5515463,-46.6765523],[-23.5514184,-46.6767454],[-23.5512709,-46.6769707],[-23.5510939,-46.6772175],[-23.5510545,-46.6773784],[-23.5508578,-46.6775393],[-23.5507693,-46.6776681],[-23.5507103,-46.6777539],[-23.5505431,-46.6780221],[-23.5503464,-46.6782904],[-23.5502185,-46.6785264],[-23.5501497,-46.6787088],[-23.5500218,-46.6790199],[-23.5499136,-46.6793311],[-23.5498251,-46.6795564],[-23.5496186,-46.679846],[-23.549412,-46.6800177],[-23.5491071,-46.6802001],[-23.5488514,-46.6802645],[-23.5484482,-46.6802967],[-23.5480843,-46.6802001],[-23.5477203,-46.6800392],[-23.5472679,-46.6799963],[-23.5469532,-46.6799963],[-23.5465106,-46.6803396],[-23.5462057,-46.680522],[-23.5459893,-46.6805863],[-23.5472778,-46.6801035],[-23.5457729,-46.6805971],[-23.5455565,-46.6806078],[-23.5456352,-46.6805005],[-23.5455565,-46.6803825],[-23.54535,-46.6803396],[-23.5450746,-46.6803396],[-23.5448779,-46.6803825],[-23.5446812,-46.68064],[-23.5444451,-46.6808438],[-23.5441894,-46.6809833],[-23.543855,-46.6810584],[-23.5435501,-46.6810799],[-23.5432353,-46.6811442],[-23.5430583,-46.6812944],[-23.5428321,-46.6813374],[-23.5426747,-46.6814339],[-23.5425271,-46.6815948],[-23.5419173,-46.6819489],[-23.5413272,-46.6822171],[-23.5410518,-46.6823351],[-23.5407173,-46.6824746],[-23.5403928,-46.6822922],[-23.5399108,-46.681906],[-23.5395567,-46.6816914],[-23.5395174,-46.6816163],[-23.539596,-46.6813481],[-23.5397633,-46.6810584],[-23.5399108,-46.6808331],[-23.5400092,-46.6806614],[-23.5401173,-46.6805112],[-23.5401075,-46.6802537],[-23.540196,-46.680007],[-23.5403239,-46.6798139],[-23.5403731,-46.6796207]]; | |
points = cloneCoords(original); | |
if(!track) { | |
track = L.polyline(points) | |
.addTo(trackLayer); | |
} else { | |
track.setLatLngs(points); | |
} | |
$("#tolerance").val(0); | |
$("#tolerance").prop("disabled", false); | |
} | |
} | |
function saveToDatabase() { | |
alert("Save path to database") | |
// insert here ajax call to send to server. | |
} | |
// HTML BINDINGS | |
$('#tolerance').on("input", simplifyPath); | |
$('#btn_load').on("click", loadFromDatabase); | |
$('#btn_save').on("click", saveToDatabase); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment