Leaflet Maps Split Route Tool
...Loading...
[Map Height : Small - Medium - Large]
Description
There is a need, when creating routes, to allow a new marker to be inserted into the route between points that have already been input. This can be achieved by splitting a single polyline into two polylines and adding a new marker mid-way between them. From then on the user can then drag each marker to a different location.
How To Use
- Draw a route by clicking points sequentially
- Once you have 2 or more points you can click the [Make a Split] option to start the split function
- Click the first...
- ...And second points in between which you wish to make the split. The order in which you click (1,2 or 2,1) is not important
- Drag the new marker (if required) to the correct location
How it Works
var map;
var messagediv=document.getElementById('messagediv');
var tileProviderURL="tile.openstreetmap.org";
var tileProviderAttribution='© <a href="//www.openstreetmap.org/copyright">OpenStreetMap</a> contributors';
var mapDivID = "map_canvas";
var latlng=[0,0];
var zoom = 2;
var routePoints=new Array(0);
var routeMarkers=new Array(0);
var mode=0; //Which mode. 0=Draw route, 1=Split Route
var polyline;
var splitpoint1;
var splitpoint2;
function initialize()
{
document.getElementById(mapDivID).style.cursor = "crosshair";
map = L.map(mapDivID,{
fullscreenControl: true,
fullscreenControlOptions: {
position: 'topleft'
}
}).setView(latlng, zoom);
L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png', {
maxZoom: 18,
attribution: 'Map data © <a href="//openstreetmap.org">OpenStreetMap</a> contributors, ' +
'<a href="//creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>',
id: 'mapbox.streets'
}).addTo(map);
var openstreetmap = L.tileLayer('https://{s}.'+tileProviderURL+'/{z}/{x}/{y}.png', {
maxZoom: 18,
attribution: tileProviderAttribution
});
openstreetmap.addTo(map);
L.control.locate().addTo(map); //Add "Show me where I am" control
map.on('click', ftnMapClicked);
polyline = L.polyline(routePoints, {color: 'red'}).addTo(map);
messagediv.innerHTML='Ready';
}
function ftnMapClicked(event)
{
var clickedLocation = event.latlng;
if (mode==0)
{
routePoints.push(clickedLocation);
messagediv.innerHTML="Adding Point ("+(routePoints.length)+")...";
ftnCheckShowSplitButton(routePoints.length);
var number=routePoints.length-1;
var marker = createMarker(clickedLocation,number);
routeMarkers.push(marker);
polyline.setLatLngs(routePoints);
}
}
function ftnCheckShowSplitButton(nopoints)
{
if (nopoints>1)
{
document.getElementById("btn_split").disabled=false;
}
else
{
document.getElementById("btn_split").disabled=true;
}
}
function ftnClearMap()
{
//Reset Points Array
routePoints=new Array(0);
//Reset Polyline
polyline.setLatLngs(routePoints);
//Remove Markers
if (routeMarkers)
{
for (i in routeMarkers)
{
routeMarkers[i].remove();
}
}
routeMarkers=new Array(0);
ftnCheckShowSplitButton(routePoints.length);
mode=0;
messagediv.innerHTML="Map Cleared";
}
function SplitRoute(pnt1,pnt2)
{
var midpoint=findmidpoint(pnt1,pnt2,0.5);
var tmppoints=[];
var insertpoint=getlowestbetween(getindexofpoint(pnt1),getindexofpoint(pnt2));
for(var i=0;i<routePoints.length;++i)
{
tmppoints.push(routePoints[i]);
if (i==insertpoint)
{
tmppoints.push(midpoint);
}
}
routePoints=tmppoints;
redisply();
messagediv.innerHTML="Split Complete";
}
function findmidpoint(point1, point2, fraction)
{
if (point1.equals(point2)) {
console.log("Click 1 and 2 are on the same marker");
return [point1.lat,point2.lng];
}
var phi1 = point1.lat.toRadians();
var phi2 = point2.lat.toRadians();
var lmd1 = point1.lng.toRadians();
var lmd2 = point2.lng.toRadians();
var cos_phi1 = Math.cos(phi1);
var cos_phi2 = Math.cos(phi2);
var angularDistance = getangularDistance(point1, point2);
var sin_angularDistance = Math.sin(angularDistance);
var A = Math.sin((1 - fraction) * angularDistance) / sin_angularDistance;
var B = Math.sin(fraction * angularDistance) / sin_angularDistance;
var x = A * cos_phi1 * Math.cos(lmd1) +
B * cos_phi2 * Math.cos(lmd2);
var y = A * cos_phi1 * Math.sin(lmd1) +
B * cos_phi2 * Math.sin(lmd2);
var z = A * Math.sin(phi1) +
B * Math.sin(phi2);
return [Math.atan2(z, Math.sqrt(Math.pow(x, 2) +
Math.pow(y, 2))).toDegrees(),
Math.atan2(y, x).toDegrees()];
};
Number.prototype.toDegrees = function() {
return this * 180 / Math.PI;
};
if (!('toRadians' in Number.prototype)) {
Number.prototype.toRadians = function() {
return this * Math.PI / 180;
};
}
function getangularDistance(point1, point2) {
var phi1 = point1.lat.toRadians();
var phi2 = point2.lat.toRadians();
var d_phi = (point2.lat - point1.lat).toRadians();
var d_lmd = (point2.lng - point1.lng).toRadians();
var A = Math.pow(Math.sin(d_phi / 2), 2) +
Math.cos(phi1) * Math.cos(phi2) *
Math.pow(Math.sin(d_lmd / 2), 2);
return 2 * Math.atan2(Math.sqrt(A), Math.sqrt(1 - A));
};
function redisply()
{
if (routeMarkers)
{
for (i in routeMarkers)
{
routeMarkers[i].remove();
}
}
routeMarkers=new Array(0);
polyline.setLatLngs(routePoints);
if (routePoints)
{
for (i in routePoints)
{
var marker=createMarker(routePoints[i],i);
routeMarkers.push(marker);
}
}
}
function getlowestbetween(i1,i2)
{
if (i1>i2)
{
return (i2);
}
else
{
return (i1);
}
}
function getindexofpoint(point)
{
var index;
for(var i=0;i<routePoints.length;++i)
{
if ((routePoints[i].lat==point.lat)&&(routePoints[i].lng==point.lng))
{
index=i;
}
}
return (index);
}
function ftnMakeSplit()
{
messagediv.innerHTML="Click on the first marker";
mode=1; // Set the mode so the next map click acts accordingly
}
function createMarker(location,num)
{
var thisMarker = L.marker(location,{title:'Marker ' + num,draggable:'true'}).addTo(map);
thisMarker.on('dragend', function(e) {
//edit the routePoint
routePoints[num]=thisMarker.getLatLng();
//update polyline
polyline.setLatLngs(routePoints);
});
thisMarker.on('click', function(e) {
//These modes need set in this order (2 then 1)
if (mode==2)
{
messagediv.innerHTML="Splitting";
splitpoint2=thisMarker.getLatLng();
SplitRoute(splitpoint1,splitpoint2);
mode=0; //BAck to normal mode.
}
//These modes need set in this order (2 then 1)
if (mode==1)
{
messagediv.innerHTML="Click on the second marker";
mode=2;
splitpoint1=thisMarker.getLatLng();
}
});
return thisMarker;
}
Relevant Links
Leaflet [https://leafletjs.com/]
Further Uses and Ideas
- None Currently
Version History
- Version 1 : 28th Septeber 2008 - Version 1
- Version 1.1 : 24th November 2009 - Fix bug where high zoom causes split to occur on marker #2 rather then the mid-point
- Version 1.2 : 15th December 2009 - Fix another split bug
- Version 2 : 10th November 2013 - Implemented Google Maps API V3
- Version 3 : 8th Janruary 2020 - Convert to Leaflet Maps