Fly to specific marker when using layers - mapbox

I use layers to clusterize my markers. I initialize like this:
this.map.addSource('markers', {
type: 'geojson',
data: this.getData(),
cluster: true,
clusterMaxZoom: 14 // Max zoom to cluster points on
});
// Add circles for clusters
this.map.addLayer({
id: 'clusters',
type: 'circle',
source: 'markers',
filter: ['has', 'point_count'],
//...
});
this.map.addLayer({
id: 'unclustered-point',
type: 'circle',
source: 'markers',
filter: ['!', ['has', 'point_count']],
//...
});
I also have a popup when a marker is clicked:
this.map.on('click', 'unclustered-point', (e) => {
// ...
new mapboxgl.Popup({ offset: 20 })
.setLngLat(coordinates)
.setDOMContent(popupContent)
.addTo(this.map);
});
Next to the map, I have a table with all my markers. When the user click on it, I want to fly to this one on the map and open its dedicated popup.
I have an identifier (which I set on the properties in my geojson).
I couldn't find any solution about this. Any ideas?
I can fly to the marker using this, but I am not very satisfied about this solution, and, mostly, I cannot open the popup.
const marker: markerProps = (this.map.getSource('marker') as any)._data.features
.map(feature => feature.properties)
.find(props => props.id === id);
this.map.flyTo({ center: [props.lon, props.lat], zoom: 14 });

There is an example in the Mapbox docs that does something very similar to what you are describing here: https://docs.mapbox.com/help/tutorials/building-a-store-locator/#define-interactivity-functions

Related

Update style of individual feature from single geoJSON source on Mapbox map, when clicked

I'm working with Mapbox GL JS to plot geoJSON data on a map using their external geoJSON example as a starting point. The geoJSON file contains lots of features which are plotted as individual markers on the same layer. I would like to highlight the clicked marker by changing its colour from red to blue. I have adapted the example to show a pop-up with the point id when clicked (just as a proof of concept that the markers can fire events when clicked), however, I'm struggling to find a way to change the styling of the individual clicked marker.
The code is currently as follows:
mapboxgl.accessToken = 'pk.eyJ1IjoiZGFuYnJhbWFsbCIsImEiOiJjbDB3ODFveHYxOG5rM2pubWpwZ2R1Y2xuIn0.yatzJHqBTjQ6F3DHASlriw';
const map = new mapboxgl.Map({
container: 'map', // container ID
style: 'mapbox://styles/mapbox/satellite-v9', // style URL
zoom: 7, // starting zoom
center: [138.043, 35.201] // starting center
});
map.on('load', () => {
map.addSource('earthquakes', {
type: 'geojson',
data: 'https://docs.mapbox.com/mapbox-gl-js/assets/earthquakes.geojson'
});
map.addLayer({
'id': 'earthquakes-layer',
'type': 'circle',
'source': 'earthquakes',
'paint': {
'circle-radius': 8,
'circle-stroke-width': 2,
'circle-color': 'red',
'circle-stroke-color': 'white'
}
});
});
map.on('click', 'earthquakes-layer', (e) => {
new mapboxgl.Popup()
.setLngLat(e.lngLat)
.setHTML('Id: ' + e.features[0].properties.id)
.addTo(map);
});
Here is a codepen: https://codepen.io/danb1/pen/BaYjOyx
Is it the case that it's actually not possible to use this approach, and instead each feature from the geoJSON file needs to be plotted as a separate layer? I'm struggling to find any examples of this and am not able to modify the geoJSON source — it has to come from one single file (rather than loading multiple geoJSON files separately on separate layers).
This is possible using feature-state. The first thing to do is to ensure the layer data contains ids for each feature (in the example the source data doesn't so we need to add generateId: true to the map.addSource method).
We then need to add mousemove and mouseleave events to the map to store the moused-over feature id (if there is one, i.e. if the mouse is hovering over a feature):
let hoveredEarthquakeId = null;
map.on('mousemove', 'earthquakes-layer', (e) => {
map.getCanvas().style.cursor = 'pointer';
if (e.features.length > 0) {
map.setFeatureState(
{ source: 'earthquakes', id: e.features[0].id },
{ hover: true }
);
hoveredEarthquakeId = e.features[0].id;
}
});
map.on('mouseleave', 'earthquakes-layer', () => {
map.getCanvas().style.cursor = '';
if (hoveredEarthquakeId !== null) {
map.setFeatureState(
{ source: 'earthquakes', id: hoveredEarthquakeId },
{ hover: false }
);
}
hoveredEarthquakeId = null;
});
Finally, in the layer properties, the colour setting of the circle needs to be updated to reflect the hover value stored against the feature:
'circle-color': [
'case',
['boolean', ['feature-state', 'hover'], false],
'#00f',
'#f00'
],
The final thing can be seen in the modified pen. There is also a MapBox tutorial covering this kind of thing in a slightly more complicated way, which I hadn't come across until now: https://docs.mapbox.com/help/tutorials/create-interactive-hover-effects-with-mapbox-gl-js/.

Check if a GeoJSON source is present in mapbox viewport

I have a map with several layers of GeoJSON each with their own unique layer name:
var map = new mapboxgl.Map({
container: 'map',
center: [-97.5651505, 37.89549,],
zoom: 4
});
var sources = {
'ord': 'chicago',
'pit': 'pittsburgh',
'atl': 'atlanta'
};
map.on('load', function () {
for (var s in sources) {
map.addSource(s, { type: 'geojson', data: `/geojson/${s}.json` });
map.addLayer({
'id': sources[s],
'type': 'fill',
'source': s,
'layout': {
'visibility': 'visible'
},
'paint': {
'fill-color': '#088',
'fill-opacity': 0.5
}
});
}
});
I would like to check if a user has zoomed in past zoom level 13 evaluate if any of these three layers is in the viewport. If it is I'll take action to add a button to the overlay. However, I'm having issues finding any documentation other than leaflet on how to check if a layer is inside the viewport. I've found some mention of markers that that doesn't seem to apply.
You can achieve this with queryRenderedFeatures which returns an array of features rendered within a given bounding box. However, if you omit the bounding box argument, queryRenderedFeatures will query within the entire viewport. You can also use the options.layers argument to limit your query to specific layers to avoid getting a bunch of features that are in the underlying style (for example, streets and lakes). You can do this query in a zoomend event listener to achieve your desired outcome. Putting it all together would look something like this:
map.on('zoomend', () => {
if (map.getZoom() > 13) {
const visibleFeatures = map.queryRenderedFeatures(null, {layers: ['ord', 'pit', 'atl']});
// if none of the layers are visible, visibleFeatures will be an empty array
if (visibleFeatures.length) {
// figure out which layers are showing and add your button
}
}
});

mapbox-gl-js create a circle around a lat/lng?

I need to create a circle around a point where a user clicks. How would I do this? Every tutorial shows extracting a circle from a geojson source and not creating one. Need to be able to edit the radius as well.
Did you try something yourself? Following the mapbox examples you should be able to get an idea of how to build something like that.
You would need to do 3 things:
Create a source that holds the data
Create a layer of type "circle" for displaying the data as circles
On every click of the user, extract the "latitude, longitude" and add a point to your data list. Then display all of those points as a circle on the map.
This is an example of how I would have coded that: https://jsfiddle.net/andi_lo/495t0dx2/
Hope that helps you out
mapboxgl.accessToken = '####';
var map = new mapboxgl.Map({
container: 'map', // container id
style: 'mapbox://styles/mapbox/light-v9', //stylesheet location
center: [-74.50, 40], // starting position
zoom: 9 // starting zoom
});
map.on('load', () => {
const points = turf.featureCollection([]);
// add data source to hold our data we want to display
map.addSource('circleData', {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: [],
},
});
// add a layer that displays the data
map.addLayer({
id: 'data',
type: 'circle',
source: 'circleData',
paint: {
'circle-color': '#00b7bf',
'circle-radius': 8,
'circle-stroke-width': 1,
'circle-stroke-color': '#333',
},
});
// on user click, extract the latitude / longitude, update our data source and display it on our map
map.on('click', (clickEvent) => {
const lngLat = new Array(clickEvent.lngLat.lng, clickEvent.lngLat.lat);
points.features.push(turf.point(lngLat));
map.getSource('circleData').setData(points);
});
});
#map {
height: 500px;
}
<div id="map"></div>

openlayers 3 with mapproxy Black area inside map

I have a problem when use Ol3 with Mapproxy and Mapnik to get the layers from OSM database.
when I convert my work from Ol2 to Ol3 I see something wrong when I zoom in/out in map I get some black area and Thank you for help :)
This is my code to show the map
map = new ol.Map({
loadTilesWhileAnimating: true,
loadTilesWhileInteracting: true,
target : "map",
controls: ol.control.defaults({
attributionOptions: /** #type {olx.control.AttributionOptions} */ ({
collapsible: false
})
}).extend([mousePositionControl]),
view: new ol.View({
projection: "EPSG:4326",
center: myLatLng,
zoom : 6
}),
overlays: [overlay],
layers: [new ol.layer.Tile({
title: "base Layer",
type: 'base',
visible: true,
source: new ol.source.TileWMS({
projection: "EPSG:4326",
url: 'http://localhost:8080/service?',
params: {
'LAYERS': "baseLayer"
}
})
})
]
});
Some image from my problem this map for Iraq
In stead of
'LAYERS'
use
LAYERS
(no quotes) in the params.
In my case I also modified the projection value.

Add custom properties to leaflet/mapbox geoJSON marker

The two functions of relevance are below.
I want to add custom properties to the markers. To be specific, the userID to the properties object. It doesn't have to be there but what i've done below does not work.
Is there no way to pass custom properties?
initMap:function(){
L.mapbox.accessToken = 'myToken';
Shop.el.map = L.mapbox.map('map');
Shop.el.retailersLayer = L.mapbox.featureLayer().addTo(Shop.el.map);
Shop.el.retailMarkers = [];
// add tile layer
L.tileLayer('myLayerKey', {
maxZoom: 18
}).addTo(Shop.el.map);
// By default, center map over Colorado
Shop.el.map.setView([39.7392, -104.9847], 12);
},
addGeoJSONToMap:function(retailers){
// format geoJSON
jQuery.each(retailers, function(index, val) {
// Push each on of the retail locations into the retailMarkers array
Shop.el.retailMarkers.push(
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [parseFloat(val.lng), parseFloat(val.lat)]
},
properties: {
'userId':'99', <------------------------ This does not work.
'marker-color': '#193441',
'marker-symbol': 'star-stroked',
title: ' val.name,
description: 'Marker Description'
},
}
);
});
// Add the retail location as markers to the map
Shop.el.retailersLayer.setGeoJSON({
type: 'FeatureCollection',
features: Shop.el.retailMarkers
});
},
Never mind. This DOES work. The property is then found in:
marker.features.userID