Mapbox - How can I change the color of a marker when the user hovers over feature in a sidebar? - mapbox

I'm creating a store locator similar to the example on their website. How would I change the color of the marker when the user hovers over a store listed?
Here is an example of the javascript being used:
/**
* Add the map to the page
*/
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/light-v10',
center: [-77.034084142948, 38.909671288923],
zoom: 13,
scrollZoom: false
});
const stores = {
'type': 'FeatureCollection',
'features': [
{
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': [-77.034084142948, 38.909671288923]
},
'properties': {
'phoneFormatted': '(202) 234-7336',
'phone': '2022347336',
'address': '1471 P St NW',
'city': 'Washington DC',
'country': 'United States',
'crossStreet': 'at 15th St NW',
'postalCode': '20005',
'state': 'D.C.'
}
},
{
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': [-77.049766, 38.900772]
},
'properties': {
'phoneFormatted': '(202) 507-8357',
'phone': '2025078357',
'address': '2221 I St NW',
'city': 'Washington DC',
'country': 'United States',
'crossStreet': 'at 22nd St NW',
'postalCode': '20037',
'state': 'D.C.'
}
},
{
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': [-77.043929, 38.910525]
},
'properties': {
'phoneFormatted': '(202) 387-9338',
'phone': '2023879338',
'address': '1512 Connecticut Ave NW',
'city': 'Washington DC',
'country': 'United States',
'crossStreet': 'at Dupont Circle',
'postalCode': '20036',
'state': 'D.C.'
}
}
]
};
/**
* Assign a unique id to each store. You'll use this `id`
* later to associate each point on the map with a listing
* in the sidebar.
*/
stores.features.forEach((store, i) => {
store.properties.id = i;
});
/**
* Wait until the map loads to make changes to the map.
*/
map.on('load', () => {
/**
* This is where your '.addLayer()' used to be, instead
* add only the source without styling a layer
*/
map.addSource('places', {
'type': 'geojson',
'data': stores
});
// Add a layer showing the places.
map.addLayer({
id: 'places',
type: 'circle',
source: 'places',
paint: {
'circle-color': '#ffc629',
'circle-radius': 9,
'circle-stroke-width': 3,
'circle-stroke-color': '#ffda61',
},
});
});
/**
* Add a marker to the map for every store listing.
**/
function addMarkers() {
/* For each feature in the GeoJSON object above: */
for (const marker of stores.features) {
/* Create a div element for the marker. */
const el = document.createElement('div');
/* Assign a unique `id` to the marker. */
el.id = `marker-${marker.properties.id}`;
/* Assign the `marker` class to each marker for styling. */
el.className = 'marker';
/**
* Create a marker using the div element
* defined above and add it to the map.
**/
new mapboxgl.Marker(el, { offset: [0, -23] })
.setLngLat(marker.geometry.coordinates)
.addTo(map);
}
}
/**
* Add a listing for each store to the sidebar.
**/
function buildLocationList(stores) {
for (const store of stores.features) {
/* Add a new listing section to the sidebar. */
const listings = document.getElementById('listings');
const listing = listings.appendChild(document.createElement('div'));
/* Assign a unique `id` to the listing. */
listing.id = `listing-${store.properties.id}`;
/* Assign the `item` class to each listing for styling. */
listing.className = 'item';
/* Add the link to the individual listing created above. */
const link = listing.appendChild(document.createElement('a'));
link.href = '#';
link.className = 'title';
link.id = `link-${store.properties.id}`;
link.innerHTML = `${store.properties.address}`;
/* Add details to the individual listing. */
const details = listing.appendChild(document.createElement('div'));
details.innerHTML = `${store.properties.city}`;
if (store.properties.phone) {
details.innerHTML += ` ยท ${store.properties.phoneFormatted}`;
}
}
}
I believe I could accomplish this with map.on('mousemove', 'places', (event) but figure it's best to ask here in case someone has done this before.
Thank you!

Related

How to use react-map-gl to draw line between two point

I am trying to draw a line between two points using react-map-gl library. I can not find example from the official document, So I am trying to reproduce same behavior from following code snippet which use Mapbox library
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v11',
center: [-122.486052, 37.830348],
zoom: 15
});
map.on('load', function() {
map.addSource('route', {
'type': 'geojson',
'data': {
'type': 'Feature',
'properties': {},
'geometry': {
'type': 'LineString',
'coordinates': [
[-122.483696, 37.833818],
[-122.493782, 37.833683]
]
}
}
});
map.addLayer({
'id': 'route',
'type': 'line',
'source': 'route',
'layout': {
'line-join': 'round',
'line-cap': 'round'
},
'paint': {
'line-color': '#888',
'line-width': 8
}
});
});
Here is the sandbox, I do not see any errors on the console but the line is not displayed:
https://codesandbox.io/s/draw-line-between-two-point-v0mbc?file=/src/index.js:214-226
The code in the sandbox actually works (for me anyway), but is misleading because the line drawn is nowhere near the viewport.
A couple of things to note are that coordinates are an array given in [long, lat] which may not be what most people would assume. For example, if you cut and paste [lat,long] from google maps for San Fransisco, you get [37.77909036739809, -122.41510269913951]. Then you'll have to reverse those and put them in:
const dataOne = {
type: "Feature",
properties: {},
geometry: {
type: "LineString",
coordinates: [
[-122.41510269913951, 37.77909036739809],
[39.5423, -77.0564]
]
}
};
Also, the sample code has some cruft in it. Edit the variable dataOne not the other unused place.
Now you'll see a line from San Fransisco to some random spot in the middle of Antarctica that was really easy to miss.
Just in case the link goes bad, the full code is:
import React, { Component } from "react";
import { render } from "react-dom";
import ReactMapGL, { Source, Layer } from "react-map-gl";
class App extends Component {
constructor(props) {
super(props);
this.state = {
viewport: {
latitude: 38.63738602787579,
longitude: -121.23576311149986,
zoom: 6.8,
bearing: 0,
pitch: 0,
dragPan: true,
width: 600,
height: 600
}
};
}
render() {
const { viewport } = this.state;
const MAPBOX_TOKEN =
"pk.eyJ1Ijoic21peWFrYXdhIiwiYSI6ImNqcGM0d3U4bTB6dWwzcW04ZHRsbHl0ZWoifQ.X9cvdajtPbs9JDMG-CMDsA";
const dataOne = {
type: "Feature",
properties: {},
geometry: {
type: "LineString",
coordinates: [
[-122.41510269913951, 37.77909036739809],
[39.5423, -77.0564]
]
}
};
return (
<ReactMapGL
{...viewport}
mapboxApiAccessToken={MAPBOX_TOKEN}
onViewportChange={(newViewport) => {
this.setState({ viewport: newViewport });
}}
>
<Source id="polylineLayer" type="geojson" data={dataOne}>
<Layer
id="lineLayer"
type="line"
source="my-data"
layout={{
"line-join": "round",
"line-cap": "round"
}}
paint={{
"line-color": "rgba(3, 170, 238, 0.5)",
"line-width": 5
}}
/>
</Source>
</ReactMapGL>
);
}
}
render(<App />, document.getElementById("root"));

Color title mapbox

Does anyone know how to change the title color of a marker on a mapbox,
I would like home to be in blue, I tried to add paint : color-size:"blue", but it doesn't work,
thank you for your help!!
map.on('load', function () {
map.loadImage(
'https://docs.mapbox.com/mapbox-gl-js/assets/custom_marker.png',
function (error, image) {
if (error) throw error;
map.addImage('custom-marker', image);
map.addSource('points', {
'type': 'geojson',
'data': {
'type': 'FeatureCollection',
'features': [
{
// feature for Mapbox
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': [6.157902659395512,49.3612254277963],
},
'properties': {
'title': 'Lieu de repas',
'scale': 2,
},
}
]
}
});
map.addLayer({
'id': 'points',
'type': 'symbol',
'source': 'points',
'layout': {
'icon-image': 'custom-marker',
'text-field': ['get', 'title'],
"text-size": 28,
'text-font': [
'Open Sans Regular',
],
'text-offset': [0, 1.25],
'text-anchor': 'top',
}
});
}
);
});
You could possibly create an HTML marker for that in a way similar to this:
var el = document.createElement('div');
el.innerHTML = 'Lieu de repas';
el.style.color = 'blue';
new mapboxgl.Marker(el)
.setLngLat([6.157902659395512,49.3612254277963])
.addTo(map);

How to draw multiple circles with different radius in MapBox GL JS?

I get some information from a AJAX and I create a GeoJson looking like
var waypointGeojson = {
'type': 'FeatureCollection',
'features': [{
'type': 'Feature',
'properties': {
'id': waypoint.poi_id,
'name': waypoint.name,
'iconSize': [100, 100]
},
'geometry': {
'type': 'Point',
'coordinates': [waypoint.longitude, waypoint.latitude],
}, {
'type': 'Feature',
'properties': {
'id': waypoint.poi_id,
'name': waypoint.name,
'iconSize': [25,25]
}, {
'geometry': {
'type': 'Point',
'coordinates': [waypoint.longitude, waypoint.latitude],
}, 'type': 'Feature',
'properties': {
'id': waypoint.poi_id,
'name': waypoint.name,
'iconSize': [35,35]
},
'geometry': {
'type': 'Point',
'coordinates': [waypoint.longitude, waypoint.latitude],
},
]
};
Then I add the source to the map like this :
map.addSource('waypoints', {
'type': 'geojson',
'data': waypointGeojson,
});
Then I loop around the Features to get the data and show the markers on my map.
waypointGeojson.features.forEach(function(marker) {
var radius = Number(marker.properties.iconSize[0]);
var latitude = Number(marker.geometry.coordinates[1]);
var el = document.createElement('div');
el.className = 'marker';
el.style.backgroundColor = 'rgba(230, 56, 18, 0.5)' ;
el.style.width = radius + 'px';
el.style.height = radius + 'px';
new mapboxgl.Marker(el)
.setLngLat(marker.geometry.coordinates)
.addTo(map);
}
At this step my map looks like :
Map at Zoom 15.32
However I want the Circle to be adjusted while I zoom in/zoom out.
For instance, if I zoom in :
Map at Zoom 17.32 The circle radius has not been adjusted (obviously!)
If you have any idea how I could do that with MapBox GL JS ?
I did try to use the formula (from here), with no success :
const metersToPixelsAtMaxZoom = (meters, latitude) =>
meters / 0.075 / Math.cos(latitude * Math.PI / 180)
If I use the method described here, then I put the
waypointGeojson.features.forEach(function(marker) {
var id = Number(marker.properties.id);
var radius = Number(marker.properties.iconSize[0]);
var latitude = Number(marker.geometry.coordinates[1])
map.addLayer({
"id": "circle"+id,
"type": "circle",
"source": "waypoints",
// "layout": {
// "visibility": "none"
// },
"paint": {
"circle-radius": {
stops: [
[0, 0],
[20, metersToPixelsAtMaxZoom(radius, latitude)]
],
base: 2
},
"circle-color": "red",
"circle-opacity": 0.4
}
});
});
I get 3 circles per point see here. This method is good because zoom in/out doesn't alter the size, but how to only have the corresponding circle on my point?

In mapbox how do I move a Feature to top (z-index wise)?

I have a layer full of state border "Features". When a user clicks on a State, I want to move that state's Feature to the top of the stack (z-index wise).
export function drawStateBorders() {
$.getJSON('https://www.mapbox.com/mapbox-gl-js/assets/us_states.geojson').then((data) => {
this.stateGeoJSON = data;
this.map
.addSource('states', {
type: 'geojson',
data,
})
.addLayer({
id: 'state-borders',
type: 'line',
source: 'states',
paint: {
'line-color': [
'case', ['boolean', ['feature-state', 'selected'], false],
'#8d8b76',
'#bfe2ab',
],
'line-width': [
'case', ['boolean', ['feature-state', 'selected'], false],
6,
3,
],
},
});
});
}
When I select the state
export function stateSelected(state) {
const stateFeatures = this.map.queryRenderedFeatures({
layers: ['state-borders'],
filter: [
'==', 'STATE_NAME', state,
],
});
const features = this.stateGeoJSON.features;
const currentFeature = stateFeatures[0];
if (!currentFeature) {
return;
}
// same state
if (currentFeature.id === this.selectedStateId) return;
// move to front HERE ?????
// old selected state
if (this.selectedStateId) {
this.map.setFeatureState({
source: 'states',
id: this.selectedStateId,
}, {
selected: false,
});
}
this.selectedStateId = currentFeature.id;
this.map.setFeatureState({
source: 'states',
id: this.selectedStateId,
}, {
selected: true,
});
}
So far I've tried
features.splice(features.indexOf(currentFeature), 1);
features.push(currentFeature);
this.map.getSource('states').setData(this.stateGeoJSON);
This seems to do some really crazy stuff to the array (duplicating some states, removing others). No idea what's happening
Adding the state to another layer worked (thanks #AndrewHarvey for the advice).
In case anyone is interested here is my code
export function stateSelected(state) {
const features = this.stateGeoJSON.features;
const currentFeature = features.find(s => s.properties.NAME === state);
if (!currentFeature) {
return;
}
// removes active layer
this.removeLayersContaining('state-borders-active');
this.drawActiveStateLayer(currentFeature);
}
export function drawActiveStateLayer(feature) {
this.map
.addLayer({
id: 'state-borders-active',
type: 'line',
source: {
type: 'geojson',
data: feature
},
paint: {
'line-color': '#8d8b76',
'line-width': 6,
},
});
}
export function drawStateBorders() {
$.getJSON('states.json').then((data) => {
this.stateGeoJSON = data;
this.map
.addSource('states', {
type: 'geojson',
data,
})
.addLayer({
id: 'state-borders',
type: 'line',
source: 'states',
paint: {
'line-color': '#bfe2ab',
'line-width': 3,
},
});
});
Also this is the shapefile I'm using: http://eric.clst.org/assets/wiki/uploads/Stuff/gz_2010_us_040_00_500k.json

How to change icon-size in mapbox gl on click?

I want to change the icon-size on map click based on the turf-nearest. How do i accomplish this? nearestBuilding.properties['icon-size'] = 0.2; does not work.
var retail = {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
properties: {
title: 'TEST',
description: 'TEST'
},
geometry: {
type: 'Point',
coordinates: [121.051779, 14.550224]
}
},
{
type: 'Feature',
properties: {
title: 'TEST',
description: 'TEST'
},
geometry: {
type: 'Point',
coordinates: [121.04568958282472, 14.552170837008527]
}
}
]
};
map.on('load', function() {
map.loadImage('https://upload.wikimedia.org/wikipedia/commons/thumb/4/40/Wiki_Loves_Earth_map_marker.svg/600px-Wiki_Loves_Earth_map_marker.svg.png', function(error, image) {
if (error) throw error;
map.addImage('marker', image);
map.addLayer({
id: 'retail',
type: 'symbol',
source: {
type: 'geojson',
data: retail
},
layout: {
'icon-image': 'marker',
'icon-size': 0.1
},
paint: { }
});
});
});
var marker = null;
map.on('click', function(e){
if(marker != null) {
marker.remove();
}
var currentLocation = {
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [e.lngLat.lng, e.lngLat.lat]
}
};
var el = document.createElement('div');
el.className = 'currLocMarker';
marker = new mapboxgl.Marker(el, { offset: [-50 / 2, -50 / 2] })
.setLngLat(currentLocation.geometry.coordinates)
.addTo(map);
var currentLocation = turf.point([e.lngLat.lng, e.lngLat.lat]);
var nearestBuilding = turf.nearest(currentLocation, retail);
var distance = turf.distance(currentLocation, nearestBuilding);
if (distance <= 0.5) {
nearestBuilding.properties['icon-size'] = 0.2;
}
});
Since icon-size supports data-driven styling(https://www.mapbox.com/mapbox-gl-js/style-spec/#layout-symbol-icon-size), have you tried doing that, with an identity function from the property on each feature? You would configure this inside the layout, instead just hard-coding 0.1.
More docs on data-driven styling is here - https://www.mapbox.com/mapbox-gl-js/style-spec/#function-type