Mapbox GL JS with Maki icons by Marker - mapbox

I generated a list of Maki Icons I want to use via the original icon editor.
drawMarkers() {
let self = this;
const mapboxgl = require("mapbox-gl");
let data = this.draw.getAll();
data.features.forEach((feature) => {
if (feature.geometry.type == "Point") {
var icon = feature.properties.icon;
var title = feature.properties.title;
if (typeof title === "undefined") {
title = "Info";
} else {
var popup = new mapboxgl.Popup({ offset: 25 }) // add popups
.setHTML(`<h3>${title}</h3>`);
var marker = new mapboxgl.Marker({
color: '#333',
draggable: false,
scale: 1,
})
.setLngLat(feature.geometry.coordinates)
.setPopup(popup)
.addTo(self.map);
}
});
The Markers are showed correctly on the Mapbox.
The GeoJSON is like this:
"type":"FeatureCollection",
"features":[
{
"id":"c749de6a6eac6b1cfdda890e7c665e0d",
"type":"Feature",
"properties":{
"icon":"ferry",
"title":"This should show a Ferry icon",
"portColor":"#d9eb37"
},
"geometry":{
"coordinates":[
6.12,
22.44
],
"type":"Point"
}
},
I want the Maki Icons also added in the Marker, but I cannot find any documentation of how icons can be used inside the Mapbox Marker.
Who can help me out? I'm using the Mapbox GL JS for Web.

It depends on how you've created your map. You can either:
Create your own map style using the Maki icons you've generated. This is done using the Mapbox studio to create your custom map style, then adding it to your application.
Create custom markers that use the maki .svg files you've created. This can be done by passing a custom element to the new mapboxgl.Marker() function. So, instead of:
var marker = new mapboxgl.Marker({
color: '#333',
draggable: false,
scale: 1,
})
you would pass:
var marker = new mapboxgl.Marker(customElement)
where customElement uses the data from your icons variable.
I'm not sure if you're using plain JS here, but there's some examples on the mapbox docs of ways you can do this.
Because you've generated your own list of Maki icons, I'd suggest downloading them and maybe host them somewhere so that you can get away with creating your markers with <img src="link to hosted maki marker"/> or something of the sort

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/.

Error with Mapbox GL integration when adding markers: "Cannot read property 'coordinates' of undefined"

I've been working on creating a dynamically updated Mapbox GL integration inside Webflow's CMS. I've succeeded in creating an array of features that can be read by Mapbox's API, but these features won't show on the map because the coordinates are not being read by the function that creates the map markers.
I receive the following error Cannot read property 'coordinates' of undefined at map-test-page:139 which is where a longitude and latitude is assigned to the current marker via this line: .setLngLat(marker.geometry.coordinates)
Partial solution was found here, but the code I've integrated into my site doesn't have featuresIn or featuresAt functions which seem to be the only way to include a includeGeometry: true parameter.
I'm curious if I need to rethink how I've created markers and do something with a function like map.on('click', ...) reference here.
Here is a minimal version that reproduces my issue.
If you're familiar with the Webflow interface you can view a read-only version of the site.
Any help would be greatly appreciated!
Here is the Mapbox script I'm using on the page:
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/strawpari/ckp2nu3dw6cwu17nt4kqwf1vv',
center: [-13.723969, 48.360542],
zoom: 2,
pitch: 0,
bearing: 0,
antialias: true,
interactive: true
});
var geojson = {
type: 'FeatureCollection',
features: farmerArray,
};
// add markers to map
geojson.features.forEach(function(marker) {
// create a HTML element for each feature
var el = document.createElement('div');
el.className = 'marker';
// make a marker for each feature and add to the map
new mapboxgl.Marker(el)
.setLngLat(marker.geometry.coordinates)
.setPopup(new mapboxgl.Popup({ offset: 25 }) // add popups
.setHTML('<img src=\'' + marker.properties.image + '\' width=\'50\' height=\'50\' border-radius=\'50%\'>' + '<h3>' + marker.properties.title + '</h3><p>' + marker.properties.description + '</p>'))
.addTo(map);
});
And here is the code embedded in each CMS item that adds a farmer's information to the farmerArray which is being read by Mapbox. Text in double-quotations "" is a placeholder for the dynamic information populated by the CMS.
var farmerArrayItem =
JSON.stringify({
type: 'Feature',
geometry: {
type: 'Point',
coordinates: ["longitude", "latitude"]
},
properties: {
title: "name",
description: "text",
image: "imagepath"
}
});
farmerArray.push(farmerArrayItem);
It doesn't appear that you have tried using the Developer Tools to debug your code. That's definitely a skill you should pick up.
With the dev tools, you will quickly see that the value of marker at the critical point is a string:
So you just need to use JSON.parse:
geojson.features.forEach(function(markerString) {
const marker = JSON.parse(markerString);
// create a HTML element for each feature
var el = document.createElement('div');
el.className = 'marker';
// make a marker for each feature and add to the map
new mapboxgl.Marker(el)
.setLngLat(marker.geometry.coordinates)

Add Maki Icon instead of Mapbox Geocoder Marker

Just a quick question, if anyone has ever replaced the Mapbox default marker with a Maki icon. I've only seen examples of using Maki icons for point tilesets/layers, but I'm wanting to use it for non-tileset features, specifically replacing the marker that adds after geocoding, at the location of the address just geocoded.
Or, trying to find something that is similar to Google Maps symbols below. Any suggestions appreciated.
var pinImage = {
path: google.maps.SymbolPath.CIRCLE,
fillColor: '#ff4b00',
fillOpacity: .9,
scale: 5,
strokeColor: '#CDDC39',
strokeWeight: 0,
strokeOpacity: .5
}
The MapboxGeocoder control has a marker option https://github.com/mapbox/mapbox-gl-geocoder/blob/master/API.md#parameters which controls the marker placed on the map when you select a result.
There is an example at https://docs.mapbox.com/mapbox-gl-js/example/custom-marker-icons/ to create a Marker with a custom icon.
So you could create an HTML Element which contains either an SVG or PNG icon from Maki and use that as your element in your custom Marker passed to the MapboxGeocoder control.
I think this sample is what you are looking for Use a custom render function with a geocoder
It allows you to add a custom render function including the icon...
var geocoder = new MapboxGeocoder({
accessToken: mapboxgl.accessToken,
types: 'poi',
// see https://docs.mapbox.com/api/search/#geocoding-response-object for information about the schema of each response feature
render: function(item) {
// extract the item's maki icon or use a default
var maki = item.properties.maki || 'marker';
return (
"<div class='geocoder-dropdown-item'><img class='geocoder-dropdown-icon' src='https://unpkg.com/#mapbox/maki#6.1.0/icons/" +
maki +
"-15.svg'><span class='geocoder-dropdown-text'>" +
item.text +
'</span></div>'
);
},
mapboxgl: mapboxgl
});
map.addControl(geocoder);

Clustering custom html markers with mapbox-gl-js

I'm using the mapbox-gl-js API and I'm using it with react to create some custom markers as follows:
let div = document.createElement('div');
let marker = new mapboxgl.Marker(div, {
offset: [ -20, 80 ]
});
marker.setLngLat(person.geometry.coordinates);
render(
<MapPersonIcon />,
div,
() => {
marker.addTo(map);
}
);
This worked great. However I would now like to cluster these markers, producing the same affect as the functionality found with layers i.e.
https://www.mapbox.com/mapbox-gl-js/example/cluster/
Does anyone know whether this is possible (hopefully with custom clusters too) or whether it will be available in an upcoming release?
This feature is now in Mapbox GL js - https://docs.mapbox.com/mapbox-gl-js/example/cluster-html/
Key takeaways:
When setting your data source using map.addSource, make sure you define cluster: true and clusterRadius: int, like so:
map.addSource( 'sourceName', {
type: "geojson",
data: {
type: 'FeatureCollection',
features: [JSON]
},
cluster: true,
clusterRadius: 80,
});
That will push mapbox to cluster your icons, but you need to tell mapbox what to do when it clusters those icons:
map.on( 'moveend', updateMarkers ); // moveend also considers zoomend
The business (trimmed down for relevance):
function updateMarkers(){
var features = map.querySourceFeatures( 'sourceName' );
for ( var i = 0; i < features.length; i++ ) {
var coords = features[ i ].geometry.coordinates;
var props = features[ i ].properties;
if ( props.cluster ){ // this property is only present when the feature is clustered
// generate your clustered icon using props.point_count
var el = document.createElement( 'div' );
el.classList.add( 'mapCluster' );
el.innerText = props.point_count;
marker = new mapboxgl.Marker( { element: el } ).setLngLat( coords );
} else { // feature is not clustered, create an icon for it
var el = new Image();
el.src = 'icon.png';
el.classList.add( 'mapMarker' );
el.dataset.type = props.type; // you can use custom data if you have assigned it in the GeoJSON data
marker = new mapboxgl.Marker( { element: el } ).setLngLat( coords );
}
marker.addTo( map );
}
NOTE: Don't copy paste this code, rather use it in conjunction with https://docs.mapbox.com/mapbox-gl-js/example/cluster-html/ to get the whole picture. Hope this helps!
Answering own question:
At current it seems that this isn't possible as per mapbox's github:
If you would like to cluster your markers you will need to use mapbox's native maki icons (please see above example picture & URL) until a plugin is available for your custom HTML markers.

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.