MapQuest/Leaflet - How to trace routes between marquers with gps coordinates? - leaflet

I have managed to integrate mapquest within my leaflet maps which was initially showing markers on the map. Below is an example with markers showing photographs taking during a trip in Namibia.
https://www.paulgodard.com/map?c=2108_DesolationValley&p=travel&m=images
Terms
Blog
Routes between marquers with gps coordinates
1 post / 0 new
Quick reply
Thu, 08/19/2021 - 06:10
#1
Paul Godard
Routes between marquers with gps coordinates
I have managed to integrate mapquest within my leaflet maps which was initially showing markers on the map. Below is an example with markers showing photographs taking during a trip in Namibia.
https://www.paulgodard.com/map?c=2108_DesolationValley&p=travel&m=images
I already have an array of locations and I would like to display the routes in between each marker. What is the best way to do this?
window.mapData = #json($mapData);
window.onload = function() {
L.mapquest.key = 'mykey';
var map = L.mapquest.map('mapOSM', {
center: [0,0],
layers: L.mapquest.tileLayer('map'),
zoom: 10
});
map.addControl(L.mapquest.control());
var mainIcon = L.Icon.extend({ options: {
iconSize: [24,24],
iconAnchor: [12,24], // half of x | full y
popupAnchor: [0,-12] // x = 0 | - half y
}});
var oms = new OverlappingMarkerSpiderfier(map);
var bounds = new L.LatLngBounds();
for (var i = 0; i < window.mapData.length; i ++) {
var datum = window.mapData[i];
var loc = new L.LatLng(datum.lat, datum.lon);
bounds.extend(loc);
var mapIconURL = '/public/assets/icons/' + datum.icon;
mapIconURL = mapIconURL.replace(/\s+/g,'');
var marker = new L.Marker(loc, { icon: new mainIcon({iconUrl: mapIconURL}) });
marker.desc = datum.popup; //JSON.parse(datum.popup);
//if ($i=0) { alert(datum.popup); }
map.addLayer(marker);
oms.addMarker(marker);
}
if (window.mapData.length > 0) {
map.fitBounds(bounds);
} else {
map.center(window.mapData[0].lat,window.mapData[0].lon);
map.zoom(1);
}
var popup = new L.Popup({closeButton: false, offset: new L.Point(0.5, -24)});
oms.addListener('click', function(marker) {
popup.setContent(marker.desc);
popup.setLatLng(marker.getLatLng());
map.openPopup(popup);
});
oms.addListener('spiderfy', function(markers) { map.closePopup(); });
oms.addListener('unspiderfy', function(markers) { });
}

You can start with the Leaflet Routing Plugin here: https://developer.mapquest.com/documentation/leaflet-plugins/routing/
Routing in Namibia might get iffy though.

Related

marker with polyline while dragging the marker using leaflet

Hi I have connection between marker with polyline like this Image .
I am attaching a sample here.
How Can I make drag possible that when I drag the the marker with polyline.
example , If I drag the marker 3 it should also update the polyline point and where ever I put the marker 3 polyline should connect with marker 3.
I need this type of drag event that can update the polyline also when dragging the marker.
I am using leaflet for this purpose but still unable to solve the dragging logic of marker with polyline.
Here is the sample code I am using
$http.get("db/getConnectionData.php").then(function (response) {
$scope.links1 = response.data.records;
// $scope.showArrow();
angular.forEach($scope.links1, function(value, i) {
var source_panoId = $scope.links1[i].s_panoId;
var dest_panoId = $scope.links1[i].d_panoId;
var sPanoID = $scope.links1[i].sourcePano_id;
var dPpanoID = $scope.links1[i].destPano_id;
angular.forEach($scope.panoramas, function(value, index) {
if($scope.panoramas[index].panoId == source_panoId){
if($scope.links.indexOf($scope.panoramas[index])== -1){
$scope.links.push($scope.panoramas[index]);
}
var SlatLang = $scope.panoramas[index].project_latLng ;
var SLatLngArr = SlatLang.split(",");
var Slat = parseFloat(SLatLngArr[0]);
var Slang = parseFloat(SLatLngArr[1]);
var polypoint1 = [Slat, Slang];
angular.forEach($scope.panoramas, function(value, index1) {
if($scope.panoramas[index1].panoId == dest_panoId){
if($scope.links.indexOf($scope.panoramas[index1])== -1){
$scope.links.push($scope.panoramas[index1]);
}
var DlatLang = $scope.panoramas[index1].project_latLng ;
var DLatLngArr = DlatLang.split(",");
var Dlat = parseFloat(DLatLngArr[0]);
var Dlang = parseFloat(DLatLngArr[1]);
var polypoint2 = [Dlat, Dlang];
// Draw seperate polyline for each connection
polyline = L.polyline([[Slat, Slang],[Dlat, Dlang]],
{
color: 'blue',
weight: 5,
opacity: .7,
}
).addTo(map);
$scope.polycoords.push(polyline);
}
});
}
});
Here is the code that I am using to make drag of marker with polyline
angular.forEach($scope.panoramas, function(value, index4){
$scope.markers[index4].on('dragstart', function(e){
var latlngs = polyline.getLatLngs(),
latlng = $scope.markers[index4].getLatLng();
for (var i = 0; i < latlngs.length; i++) {
if (latlng.equals(latlngs[i])) {
this.polylineLatlng = i;
}
}
});//dragstart
$scope.markers[index4].on('drag', function(e){
var latlngs = polyline.getLatLngs(),
latlng = $scope.markers[index4].getLatLng();
latlngs.splice(this.polylineLatlng, 1, latlng);
polyline.setLatLngs(latlngs);
});//drag
$scope.markers[index4].on('dragend', function(e){
delete this.polylineLatlng;
});//dragEnd
});
First, when creating the marker, remember to pass the draggable option as true, like this:
var marker = L.marker(latLng, { draggable: true });
Now, check which drag event you want to attach a listener to and then call the redraw function of the polyline inside the callback, like this:
// var polyline defined somewhere
marker.on('drag', function (e) {
polyline.redraw();
});
If this doesn't work, please provide sample code so we can work around with it.
Edit
You also need to change the coordinates of the polyline, otherwise redraw will do nothing. Check out this answer on SO and see if it fits your needs.
Edit 2
You're using an array of polylines while the answer just uses one polyline which has the array of coordinates, so in your case you need to use two loops to accomplish the same task. You can make this faster and maybe use an object as a lookup table to get the right polyline for each marker, for example, like this:
var table = {};
// ...
table[marker] = polyline;
Then later you can get the polyline used for each marker. But anyway, here's what I think would work in your case the way it is in the sample (it was a little hard to understand but I hope it works for you).
I don't know where you are putting the second part of your sample (the event handlers) but I assume it's not inside the double loop that is creating the polylines, right? So this is what I came up with:
marker.on('dragstart', function (e) {
var markerLatLng = marker.getLatLng();
this.polylineLatLngs = [];
for (var i = 0; i < $scope.polycoords.length; i++) {
var polyline = $scope.polycoords[i];
var latLngs = polyline.getLatLngs()
for (var j = 0; j < latLngs.length; j++) {
if (markerLatLng.equals(latLngs[j])) {
this.polylineLatLngs.push([i, j]);
}
}
}
});
marker.on('drag', function (e) {
for (var i = 0; i < this.polylineLatLngs.length; i++) {
var polyline = $scope.polycoords[this.polylineLatLngs[i][0]];
var latLngs = polyline.getLatLngs();
var markerLatLng = marker.getLatLng();
latLngs.splice(this.polylineLatLngs[i][1], 1, markerLatLng);
polyline.setLatLngs(latLngs);
}
});
I am getting this type of behavior. Please let me know how I can solve this .
Thank you for your time.
This is the polyline created by getting data from db or by making the connection between panorama.
This Image when I start dragging the marker 2 I got the result like this
This image when I dragged the marker 3.
This type of result I am getting using the source code you provided above.

leaflet snap polyline to rout

I'm try to convert polyline to rout but I have a problem
You see in the picture below that the road is different from the polyline:
Here is my code:
var mymap = L.map('map').setView([32.661343, 51.680374], 6);
L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(mymap);
var markers = new L.MarkerClusterGroup();
var markerList = [];
var a = [];
var myTrip = [];
var myTrip2 = [];
for (var i = 1; i < locations.length ; i++) {
myTrip.push(new L.LatLng(parseFloat(locations[i]['Received']['lat']),parseFloat(locations[i]['Received']['lng'])));
a[0] = parseFloat(locations[i]['Received']['lat']);
a[1] = parseFloat(locations[i]['Received']['lng']);
var marker = new L.Marker(new L.LatLng(a[0], a[1]));
marker.bindPopup((locations[i]['Received']['id']).toString());
markerList.push(marker);
var polyline =L.polyline(myTrip, {color: 'blue'}).addTo(mymap);
}
var markerPatterns = L.polylineDecorator(myTrip, {
patterns: [
{offset: 25, repeat: 50, symbol: L.Symbol.arrowHead({pixelSize: 15, pathOptions: {fillOpacity: 1, weight: 0}})}
]
}).addTo(mymap);
var control = L.Routing.control({
waypoints: myTrip,
show: false,
waypointMode: 'snap',
showAlternatives: true,
useZoomParameter: true,
createMarker: function() {}
}).addTo(mymap);
1) So are the lines drawn using the raw coordinates? In other words, are the lines drawn like you do not use a routing service?
2) Leaflet Routing Machine is a plug-in that supports several routing engines, with OSRM as a default.
http://www.liedman.net/leaflet-routing-machine/
Have you tried inserting your DB coordinates to the OSRM-demo? Is it giving you the expected results?
http://map.project-osrm.org/
The high and low streets are routes because 1 of your waypoints are hitting that street and to get back on track again it have to turn around the block. It would have routed correcly if your waypoints where more accurate.

How to remove L.rectangle(boxes[i])

I few days ago I implement a routingControl = L.Routing.control({...}) which works perfect for my needs. However I need for one of my customer also the RouteBoxer which I was also able to implement it. Now following my code I wants to remove the boxes from my map in order to draw new ones. However after 2 days trying to find a solution I've given up.
wideroad is a param that comes from a dropdown list 10,20,30 km etc.
function routeBoxer(wideroad) {
this.route = [];
this.waypoints = []; //Array for drawBoxes
this.wideroad = parseInt(wideroad); //Distance in km
this.routeArray = routingControl.getWaypoints();
for (var i=0; i<routeArray.length; i++) {
waypoints.push(routeArray[i].latLng.lng + ',' + routeArray[i].latLng.lat);
}
this.route = loadRoute(waypoints, this.drawRoute);
}; //End routeBoxer()
drawroute = function (route) {
route = new L.Polyline(L.PolylineUtil.decode(route)); // OSRM polyline decoding
boxes = L.RouteBoxer.box(route, this.wideroad);
var bounds = new L.LatLngBounds([]);
for (var i = 0; i < boxes.length; i++) {
**L.rectangle(boxes[i], {color: "#ff7800", weight: 1}).addTo(this.map);**
bounds.extend(boxes[i]);
}
console.log('drawRoute:',boxes);
this.map.fitBounds(bounds);
return route;
}; //End drawRoute()
loadRoute = function (waypoints) {
var url = '//router.project-osrm.org/route/v1/driving/';
var _this = this;
url += waypoints.join(';');
var jqxhr = $.ajax({
url: url,
data: {
overview: 'full',
steps: false,
//compression: false,
alternatives: false
},
dataType: 'json'
})
.done(function(data) {
_this.drawRoute(data.routes[0].geometry);
//console.log("loadRoute.done:",data);
})
.fail(function(data) {
//console.log("loadRoute.fail:",data);
});
}; //End loadRoute()
Well, my problem is now how to remove previously drawn boxes in order to draw new ones because of changing the wideroad using a dropdown list. Most of this code I got from the leaflet-routeboxer application.
Thanks in advance for your help...
You have to keep a reference to the rectangles so you can manipulate them (remove them) later. Note that neither Leaflet nor Leaflet-routeboxer will do this for you.
e.g.:
if (this._currentlyDisplayedRectangles) {
for (var i = 0; i < this._currentlyDisplayedRectangles.length; i++) {
this._currentlyDisplayedRectangles[i].remove();
}
} else {
this._currentlyDisplayedRectangles = [];
}
for (var i = 0; i < boxes.length; i++) {
var displayedRectangle = L.rectangle(boxes[i], {color: "#ff7800", weight: 1}).addTo(this.map);
bounds.extend(boxes[i]);
this._currentlyDisplayedRectangles.push(displayedRectangle);
}
If you don't store a reference to the L.rectangle() instance, you obviously won't be able to manipulate it later. This applies to other Leaflet layers as well - not storing explicit references to Leaflet layers is a usual pattern in Leaflet examples.

How do I get the bounding box of a mapboxgl.GeoJSONSource object?

I'm setting up a Mapbox GL JS map like this:
mapboxgl.accessToken = 'pk.my_token';
var cityBoundaries = new mapboxgl.GeoJSONSource({ data: 'http://domain.com/city_name.geojson' } );
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v8',
center: [cityLongitude,cityLatitude],
zoom: 13
});
Then I'm loading that GeoJSON data onto the map after it loads like this:
map.on('style.load', function(){
map.addSource('city', cityBoundaries);
map.addLayer({
'id': 'city',
'type': 'line',
'source': 'city',
'paint': {
'line-color': 'blue',
'line-width': 3
}
});
});
At this point, I have a map that's centered at the location I specified in new mapboxgl.Map, and it's at zoom level 13. So, only a piece of the GeoJSON data is visible on the map. I'd like to re-center and re-zoom the map so that the entire GeoJSON data is visible.
In Mapbox JS, I would do this by loading the GeoJSON data into a featureLayer and then fitting the map to its bounds with:
map.fitBounds(featureLayer.getBounds());
The fitBounds documentation for Mapbox GL JS indicates that it wants the bounds in the format of [[minLng, minLat], [maxLng, maxLat]].
Is there a way to determine the mix/max latitude & longitude values of this GeoJSON layer?
Based on the 'Obtaining a bounding box' section of this post, I've come up with this process...
map.on('style.load', function(){
$.getJSON('http://citystrides.dev/city_name.geojson', function(response){
var boundingBox = getBoundingBox(response);
var cityBoundary = new mapboxgl.GeoJSONSource({ data: response } );
map.addSource('city', cityBoundary);
map.addLayer({
'id': 'city',
'type': 'line',
'source': 'city',
'paint': {
'line-color': 'blue',
'line-width': 3
}
});
map.fitBounds([[boundingBox.xMin, boundingBox.yMin], [boundingBox.xMax, boundingBox.yMax]]);
})
});
function getBoundingBox(data) {
var bounds = {}, coords, point, latitude, longitude;
for (var i = 0; i < data.features.length; i++) {
coords = data.features[i].geometry.coordinates;
for (var j = 0; j < coords.length; j++) {
longitude = coords[j][0];
latitude = coords[j][1];
bounds.xMin = bounds.xMin < longitude ? bounds.xMin : longitude;
bounds.xMax = bounds.xMax > longitude ? bounds.xMax : longitude;
bounds.yMin = bounds.yMin < latitude ? bounds.yMin : latitude;
bounds.yMax = bounds.yMax > latitude ? bounds.yMax : latitude;
}
}
return bounds;
}
Here's a walkthrough of what the code is doing, for anyone out there who needs a detailed explanation:
map.on('style.load', function(){
When the map loads, let's do the stuff in this function.
$.getJSON('http://citystrides.dev/city_name.geojson', function(response){
Get the city's GeoJSON data. This is an asynchronous call, so we have to put the all the code that uses this data (the response) inside this function.
var boundingBox = getBoundingBox(response);
Get the bounding box of this GeoJSON data. This is calling the , function(){ that appears after the 'map on style load' block.
var cityBoundary = new mapboxgl.GeoJSONSource({ data: response } );
Build Mapbox's GeoJSON data.
map.addSource('city', cityBoundary);
Add the source to Mapbox.
map.addLayer({
Add the layer to Mapbox.
map.fitBounds([[boundingBox.xMin, boundingBox.yMin], [boundingBox.xMax, boundingBox.yMax]]);
Adjust the map to fix the GeoJSON data into view.
function getBoundingBox(data) {
This function iterates over the returned GeoJSON data, finding the minimum and maximum latitude and longitude values.
One thing to note in the getBoundingBox function is this line:
coords = data.features[i].geometry.coordinates;
In the original post, linked above, this line was written as coords = data.features[i].geometry.coordinates[0]; because their data for the list of coordinates was an array of arrays. My data isn't formatted that way, so I had to drop the [0]. If you try this code & it blows up, that might be the reason.
You can use the turf.js library. It has a bbox function:
const bbox = turf.bbox(foo);
https://turfjs.org/docs/#bbox
I use the turf-extent library, which is maintained by the Mapbox bunch anyhow. https://www.npmjs.com/package/turf-extent is the node module link.
In your code you simply import(ES6) or require as so:
ES6/Webpack: import extent from 'turf-extent';
Via script tag: `<script src='https://api.mapbox.com/mapbox.js/plugins/turf/v2.0.2/turf.min.js'></script>`
Then feed your response to the function, for example:
ES6/Webpack: let orgBbox = extent(response);
Normal: var orgBbox = turf.extent(geojson);
Then you can use the array values to set your map center:
center: [orgBbox[0], orgBbox[1]]
Or as you want, to fit bounds:
map.fitBounds(orgBbox, {padding: 20});
Here is an example using the turf.min.js in a regular html tag in case you are not using webpack or browser:
https://bl.ocks.org/danswick/83a8ddff7fb9193176a975a02a896792
Happy coding and mapping!
Based on James Chevalier's answer. For polygon/multipolygon tilesets that are assigend to a map in Mapbox Studio I am using this to get the bounding box:
getPolygonBoundingBox: function(feature) {
// bounds [xMin, yMin][xMax, yMax]
var bounds = [[], []];
var polygon;
var latitude;
var longitude;
for (var i = 0; i < feature.geometry.coordinates.length; i++) {
if (feature.geometry.coordinates.length === 1) {
// Polygon coordinates[0][nodes]
polygon = feature.geometry.coordinates[0];
} else {
// Polygon coordinates[poly][0][nodes]
polygon = feature.geometry.coordinates[i][0];
}
for (var j = 0; j < polygon.length; j++) {
longitude = polygon[j][0];
latitude = polygon[j][1];
bounds[0][0] = bounds[0][0] < longitude ? bounds[0][0] : longitude;
bounds[1][0] = bounds[1][0] > longitude ? bounds[1][0] : longitude;
bounds[0][1] = bounds[0][1] < latitude ? bounds[0][1] : latitude;
bounds[1][1] = bounds[1][1] > latitude ? bounds[1][1] : latitude;
}
}
return bounds;
}

Leaflet & Mapbox: OpenPopup not working

I've an issue with the leaflet openPopup method.
showMap = function(elements) {
var jsonp = 'http://a.tiles.mapbox.com/v3/blahblahblah.jsonp';
var m = new L.Map("my_map").setView(new L.LatLng(51.5, -0.09), 15);
var geojsonLayer = new L.GeoJSON();
var PlaceIcon = L.Icon.extend({
iconSize: new L.Point(25, 41),
shadowSize: new L.Point(40, 35),
popupAnchor: new L.Point(0, -30)
});
var icon = new PlaceIcon(__HOME__ + '/images/leaflet/marker.png');
var marker;
for (var i = 0; i < elements.length; i++) {
var address = $("<div/>").html(elements[i].address).text();
var latlng = new L.LatLng(elements[i].latitude, elements[i].longitude);
marker = new L.Marker(latlng, {icon: icon}).bindPopup(address);
if (i == 0) {
marker.openPopup();
}
m.addLayer(geojsonLayer).addLayer(marker);
}
// Get metadata about the map from MapBox
wax.tilejson(jsonp, function(tilejson) {
m.addLayer(new wax.leaf.connector(tilejson));
});
}
When I click on a marker I have the popup open. But I would like to have the first popup open when the map is loaded. (and open other popups on markers click)
AnNy ideas ?
Put openPopup call after you add the marker to the map and you should be fine.
I'm assuming that when you click on a marker you see the popup but you're not getting the popup of the first marker to show automatically when the map is loaded?
First, it doesn't look like you're actually using GeoJSON so a GeoJSON layer isn't necessary (you can just use a FeatureLayer) but that shouldn't cause any issues. Whatever layer group you use you should only be adding it to the map once and then adding all child layers to the LayerGroup. You're currently adding the geojsonLayer multiple times in your "for" loop which you don't want to do.
Second, you have to call marker.openPopup() after the marker is added to the map.
Try changing your code around to looks something like this:
var layerGroup = new L.FeatureGroup();
layerGroup.addTo( m );
for (var i = 0; i < elements.length; i++) {
var address = $("<div/>").html(elements[i].address).text();
var latlng = new L.LatLng(elements[i].latitude, elements[i].longitude);
marker = new L.Marker(latlng, {icon: icon}).bindPopup(address);
//You don't add the marker directly to the map. The layerGroup has already
//been added to the map so it will take care of adding the marker to the map
layerGroup.addLayer( marker );
if (i == 0) {
marker.openPopup();
}
}
I had this issue and fixed it with adding a timeout right after I added the marker on the map.
marker.addTo(this.map).bindPopup('Info');
setTimeout(() => {
marker.openPopup();
}, 500);
I don't know why but on some page, I need to apply timeout. In any case it's my workaround, hope this works for some of you too.
First add your map then put openPopup():
L.marker([lat, long]).bindPopup('Your message').addTo(map).openPopup();