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

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;
}

Related

MapQuest/Leaflet - How to trace 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
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.

Changing overlay layers when switching base layer

I have built a leaflet map with two base layers, and each of these base layers will have their own unique points of interest. The points of interest are being stored as geojson that I loop over to create multiple overlays for different categories. So when viewing the default base layer you would see layers for Show All, Cat1, Cat2 etc.
I need a way to be able to attach overlay layers to a base layer, or remove all overlay layers and then load the relevant ones when the base layer changes.
I tried using the following, which worked to switch categories, with the baselayerchange event, but the overlay layers were still displaying when I switched base layers.
layerControl._layers.forEach(function(layer){
if(layer.overlay){
map.removeLayer(layer.layer)
}
});
I've been searching for a couple of days now for an answer to this without any luck, any help is greatly appreciated.
EDIT
Posting additional code for context. This is not the entirety of the code, there are some plugins that I'm not including code for and have excluded definitions for a several variables, but this should provide better insight into how things are working.
//Initialize the map
var map = new L.Map('map', {
maxZoom: mapMaxZoom,
minZoom: mapMinZoom,
crs: crs1848,
attributionControl: false,
layers: [pano1848]
});
//add controls to the map
var layerControl = L.control.layers(null, null, {position: 'bottomleft'}).addTo(map);
//building category layers from geojson
var types = ['African Americans', 'Art Architecture Culture', 'Education Religion Reform', 'Everyday Life', 'Immigrants', 'Science Inventions', 'Transportation Industry Commerce'];
types.forEach(function(type){
var catType = type.replace(/\s/g,"");
var catPoints = L.geoJson(mapData, {
filter: function(feature, layer){
var cat = feature.properties['category'];
return cat.indexOf(catType) >= 0;
},
onEachFeature: function (feature, layer) {
layer.bindTooltip(feature.properties.name);
(function(layer, properties){
//Create Numeric markers
var numericMarker = L.ExtraMarkers.icon({
icon: 'fa-number',
markerColor: 'yellow',
number: feature.properties['id']
});
layer.setIcon(numericMarker);
layer.on('click', function() {
$.ajax({
url:feature.properties['url'],
dataType:'html',
success: function(result){
$('#detailContainer').html(result);
$('#overlay').fadeIn(300);
}
});
});
})(layer, feature.properties);
}
});
layerControl.addOverlay(catPoints, catType);
});
//Base Layer Change Event
map.on('baselayerchange', function(base){
var layerName;
layerControl._layers.forEach(function(layer){
if(layer.overlay){
map.removeLayer(layer.layer)
}
});
if(base._url.indexOf('1848') >= 0){
map.options.crs = crs1848;
map.fitBounds([
crs1848.unproject(L.point(mapExtent1848[2], mapExtent1848[3])),
crs1848.unproject(L.point(mapExtent1848[0], mapExtent1848[1]))
]);
var southWest = map.unproject([0, 8192], map.getMaxZoom());
var northEast = map.unproject([90112, 0], map.getMaxZoom());
map.setMaxBounds(new L.LatLngBounds(southWest, northEast));
map.addLayer(allPoints);
layerName = '1848 Panorama';
}
else if(base._url.indexOf('2018') >= 0){
map.options.crs = crs2018;
map.fitBounds([
crs2018.unproject(L.point(mapExtent2018[2], mapExtent2018[3])),
crs2018.unproject(L.point(mapExtent2018[0], mapExtent2018[1]))
]);
var southWest = map.unproject([0, 8192], map.getMaxZoom());
var northEast = map.unproject([49152, 0], map.getMaxZoom());
map.setMaxBounds(new L.LatLngBounds(southWest, northEast));
layerName = '2018 Panorama'
}
miniMap.changeLayer(minimapLayers[layerName]);
//map.setView(map.getCenter(), map.getZoom());
});
You may create global variable call "overlays", and remove it like an example below.
Here is the similar example to illustrate your problem jsFiddle
var overlays = {
'Name 1': catPoints,
'Name 2': catType
};
L.control.layers(null, overlays).addTo(map);
// Whenever you want to remove all overlays:
for (var name in overlays) {
map.removeLayer(overlays[name]);
}

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.

style a geojson point like a POI with leaflet/mapbox

I am using mapbox.js to render a mapbox map. I am trying to load geojson from my server that contain either a country polygon or a city coordinates (lon,lat).
I have been able to style the country polygons but not the city points/markers.
I am not able to modify the geojson to use mapbox simplestyle
Here is the code executed when the page loads (I changed the mapbox map ID):
var southWest = L.latLng(-90, -360), northEast = L.latLng(90, 360);
var bounds = L.latLngBounds(southWest, northEast);
var map = L.mapbox.map('map', 'MapboxMapID', { zoomControl: false, infoControl: true, detectRetina: true, maxBounds: bounds, minZoom: 2, legendControl: {position: 'topright'}});
new L.Control.Zoom({ position: 'bottomright' }).addTo(map);
map.fitBounds(bounds);
var locationsGroup = L.featureGroup().addTo(map);
and then when the user selects a country or city with a selectbox:
$("#select2-search-up").on("change", function (e) {
if (e.added) {
var location = L.mapbox.featureLayer().loadURL('/citiesCountriesID/' + e.added.id).on('ready', function(featLayer) {
this.eachLayer(function(polygon) {
polygon.setStyle({
stroke:false, fillColor:'red', fillOpacity:0.2
});
});
});
locationsGroup.addLayer(location);
} else {
locationsGroup.eachLayer(function (layer) {
if (layer._geojson[0]._id == e.removed.id) {
locationsGroup.removeLayer(layer);
}
});
}
});
Ideally I would like to display a different icon that the standard marker, but I could do with a small red square
Thank you for your inputs
In this example I did some circle markers but I'm pretty sure you can do other basic svg shps or your own .png pretty easy. http://bl.ocks.org/mpmckenna8/db2eef40314fe24e9177
This example from Mapbox also shows how to use a icon from their icon Library which has a lot of choices also. https://www.mapbox.com/mapbox.js/example/v1.0.0/l-mapbox-marker/
It might also help to see some of your geojson structure to see why it can't use simplestyle
In my bl.ocks example I loaded each of the geojson datasets separately
var begin = L.geoJson(start,{
pointToLayer:function(feature, latlng){
return L.circleMarker(latlng,{
radius:9,
fillColor: "orange",
fillOpacity:.7
})
}
})
Is how I set up my circles and I set a different L.geoJson to the other data which I left as the default markers.

Start-Marker from geojson polyline

i have a map with some walking- and bike-routes and popups with a few details an a pic. Now i want to set a marker on the first vertex of a geojson polyline, but i cant find out how. Btw. i´m new to leaflet/mapbox, and i put my map togehter from code snippets.
Here is the map now:
This is how i create the polylines now. I call them via layercontrol.
var mtb = L.geoJson(radjs, {
filter: function (feature, layer) {
if (feature.properties) {
// If the property "underConstruction" exists and is true, return false (don't render features under construction)
return feature.properties.typ === 'mtb';
}
return true;
},
style: {
"color": '#6699cc',
dashArray: '',
"weight": 4,
"opacity": 0.6
}, onEachFeature: onEachFeature2}).addTo(rad);
Thank you for your help,
Marc
You could just add a Feature to your geojson with the latitude and longitude like the do it in the Leaflet GeoJSON Example.
Probably it will be more convenient to read the geometry.coordinates from geojson features and define a new marker.
Beside you have an error on line 569. You should change:
var map = L.mapbox.map('map','',{
to:
var map = L.mapbox.map('map',null,{
Create a simple marker object and add it to the map.
marker = L.marker([0, 0], {
icon: L.mapbox.marker.icon()
}).addTo(map)
Then set the latitude and longitude to the first coordinates of your geoJSON. Assuming your geoJSON is set to the var geojson, you can access the coordinates by indexing into the array.
marker.setLatLng(L.latLng(
geojson.coordinates[0][1],
geojson.coordinates[0][0]))