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

  1. Draw a route by clicking points sequentially
  2. Once you have 2 or more points you can click the [Make a Split] option to start the split function
  3. Click the first...
  4. ...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
  5. 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='&copy; <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 &copy; <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

Comments For This Page

Pretty and pretty much useless.
On 4th April 2021

Add your own comment below and let others know what you think: