Connect markers with a polyline in Mapbox GL - reactjs

I'm developing a web application using Mapbox GL, more specifically, its binding for React, react-map-gl.
One of the planned functionalities for the app is adding markers and connecting them.
However, I'm having trouble connecting markers.
I want to start drawing the line when I click on a marker, add a breakpoint to the line when I click elsewhere and finish the line when I click on another marker.
What can I use for this?

I am also working on same, you can use deck.gl for plotting lines on map, or you can also use geoJson for the same.

What I ended up doing was using an EditableGeoJsonLayer with the features for both the markers and the connections between them as follows:
data: {
type: "FeatureCollection",
features: markers.flatMap((marker) => {
// Map markers
let features = [
{
geometry: {
type: "Point",
coordinates: marker.coordinates
},
type: "Feature",
node: marker
}
];
// Map connections
if (marker.connections.length > 0) {
features = features.concat(
marker.connections.flatMap((endMarker) => [
{
geometry: {
type: "LineString",
coordinates: [
marker.coordinates,
endMarker.coordinates
]
},
type: "Feature"
}
])
);
}
return features;
})
}

Related

How to filter features from coordinates

I'm using mapbox-gl and mapbox gl draw.
On the map, there will be a layer which has all markers. And I implemented polygon draw on that map using mapbox-gl-draw.
After that, I use turf.js to get the points within the drawn pologon. So, now, I have those points.
But those points are just points, not fully features. I want to query that features using those points.
Let's say I get these coordonites points as a result of turf.
[
[2, 2],
[3, 3],
[4, 4]
]
On the above mentioned layer, I've added markers and one of those looks like below:
{
type: "Feature",
properties: {
title: name,
id: id,
},
geometry: { coordinates: [long, lat], type: "Point" },
}
So, how can I get those features using above points?
This data-wrangling task comes up a lot in spatial web development. All you need to do is build out a geojson Point feature for each set of coordinates. You can use Array.map() for this:
const pointFeaturesArray = coordinatesArray.map((coordinates) => {
return {
type: 'Feature',
geometry: {
type: 'Point',
coordinates: coordinates
},
properties: {}
}
})
pointFeaturesArray is now an array of valid geojson point features, but what you probably want is a FeatureCollection (an array of geojson features is not valid geojson on its own). To do that, just set this arrray to the features property in a FeatureCollection:
const pointsFC = {
type: 'FeatureCollection',
features: pointFeaturesArray
}
You may want to test this geojson to make sure it is valid. One way to do this is to log it to the console, then copy and paste it into geojson.io. If it is valid it will appear on the map, if it is not valid, you will see some red highlighting in the code editor area telling you where something is wrong.
pointsFC is now ready to use in map.addSource():
map.addSource('mypointfeatures', {
type: 'geojson',
source: pointsFC
}

Remove Lines from MapboxGL map

I have a React function like the following. When a button is clicked, this function places a square on a map of the world at the specified coordinates (see code). However, I want to be able to press a different button and have the square removed. How can I do this? Is there a way to delete things from MapboxGL maps? If so, what function can I use?
The square is rendered using a function from MapboxGL and the web app is made using React JS.
React.useEffect(() => {
if(props.show) {
console.log(props);
var northEast = [131.308594, 46.195042];
var southEast = [117.597656, 8.233237];
var southWest = [79.101563, 32.842674];
var northWest = [86.847656, 44.715514];
// Add bounding box to the map
// Defines bounding box
globalMap.addSource('newroute', {
'type': 'geojson',
'data': {
'type': 'Feature',
'properties': {},
'geometry': {
'type': 'LineString',
'coordinates': [
northEast, southEast, southWest, northWest, northEast
]
}
}
});
// Draws bounding box
globalMap.addLayer({
'id': 'newroute',
'type': 'line',
'source': 'newroute',
'layout': {
'line-join': 'round',
'line-cap': 'round'
},
'paint': {
'line-color': '#ff0000',
'line-width': 5
}
});
}
});
Mapbox-GL has a remove layer method.
// If a layer with ID 'target-layer' exists, remove it.
if (map.getLayer('target-layer')) map.removeLayer('target-layer');
Another way to approach the issue is to keep the old layer, and update the data of the source with the new data (if your use-case allows for it).
"Remove layer" by setting the data of the source to an empty dataset:
map.getSource('target-source').setData({ type: "FeatureCollection", features: [] });
You can then set the data to a populated GeoJSON object if and when you have new data...

Mapbox layer not updating after source update

I'm using Redux state to update an array of coordinates in a Mapbox source. I initially check if there is a source with the id, if yes, I set the data of the source, if not I add the source to the map. When the redux state is changed, it triggers an effect which updates the coordinates of the features in the geojson object and uses setData to change the source. I've tried removing the layer, changing source and adding the layer, which just gave me the old layer (even though the source had indeed been updated). I also tried just updating the source alone and seeing if the layer would update dynamically, it did not.
Here is the code for the effect, which is triggered when the redux state is changed.
useEffect(() => {
const geoJsonObj = {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: []
}
};
for (let i = 0; i < (props.mapRoutes.length); i++) {
geoJsonObj.data.features.push({
type: 'Feature',
geometry: {
type: 'LineString',
coordinates: props.mapRoutes[i].geometry.coordinates
}
});
};
const routeLayer = {
id: 'route',
type: 'line',
source: 'route',
layout: {
'line-join': 'round',
'line-cap': 'round'
},
paint: {
'line-color': '#ff3814',
'line-width': 5,
'line-opacity': 0.75
}
};
const jsonString = JSON.stringify(geoJsonObj);
const jsonObj = JSON.parse(jsonString);
if (props.mapRoutes.length) {
if (map.current.getSource('route')) {
map.current.getSource('route').setData(jsonObj);
} else {
map.current.addSource('route', jsonObj);
map.current.addLayer(routeLayer);
};
};
}, [props.mapRoutes]);
Neither of these worked and I am having trouble finding how to update a layer based on an updated source. Everything seems right when I inspect the source in the console, I just can't manage to update the layer on the map.
Any help would be appreciated.
I found the problem, I was using the original geoJson object for the setData method instead of the data entry, which was one level too high in the object. Just a simple error which was overlooked.

How to pass an array of coordinates to react-map-gl heatmap layer?

having some trouble reconciling the docs to my use-case. I got a little stuck trying to get openstreet maps into react using d3, and have been playing around with react-map-gl...great library that's pretty dialed-in! This library is built on top of d3 and openstreetmaps and uses a lot of d3 plugins...here's the example I am trying to replicate:
https://github.com/uber/react-map-gl/blob/5.0-release/examples/heatmap/src/app.js
In this example, the data where the coordinates live is in a geoJson file, and it is accessed in a method that looks like this (Copied and pasted from the link above...in this code they are using the d3-request plugin to fetch and parse through the geoJson file, which contains other data about earthquakes etc):
_handleMapLoaded = event => {
const map = this._getMap();
requestJson(
'https://docs.mapbox.com/mapbox-gl-js/assets/earthquakes.geojson',
(error, response) => {
if (!error) {
// Note: In a real application you would do a validation of JSON data before doing anything with it,
// but for demonstration purposes we ingore this part here and just trying to select needed data...
const features = response.features;
const endTime = features[0].properties.time;
const startTime = features[features.length - 1].properties.time;
this.setState({
earthquakes: response,
endTime,
startTime,
selectedTime: endTime
});
map.addSource(HEATMAP_SOURCE_ID, {type: 'geojson', data: response});
map.addLayer(this._mkHeatmapLayer('heatmap-layer', HEATMAP_SOURCE_ID));
}
}
);
};
This is great if you are using GeoJson, and I have done this quite a bit to point d3 towards an object for US states, counties, or zipcodes...However what I am trying to do is much simpler! I have an array of data that I'm fetching, and passing down as props to this heatmap component, and it looks something like this:
[
{name: locationOne, latitude: 1.12345, longitude: -3.4567},
{name: locationTwo, latitude: 1.2345, longitude: -5.678},
...etc
]
So the question is, if I am not using geoJson, how do I tell the heatmap what coordinates to use? Any help is appreciated!!!
Even though the data in your array isn't geoJson, we can manipulate it into geoJSON. We can do this by creating a factory function to return valid geoJSON using the array data.
Once the data is converted to geoJSON it can be used as shown in the example you've found.
const rawData = [
{name: 'Feature 1', value: 2, latitude: 1.12345, longitude: -3.4567},
{name: 'Feature 2', value: 5, latitude: 1.2345, longitude: -5.678},
];
const makeGeoJSON = (data) => {
return {
type: 'FeatureCollection',
features: data.map(feature => {
return {
"type": "Feature",
"properties": {
"id": feature.name,
"value": feature.value
},
"geometry": {
"type": "Point",
"coordinates": [ feature.latitude, feature.longitude]
}
}
})
}
};
const myGeoJSONData = makeGeoJSON(rawData);
console.log(myGeoJSONData);

GeoJSON marker is not showing on my leafletjs map

I'm writing my first larger project in react and I need to set up markers in my map component. I've set everythin up as it is shown in the tutorial however it is not working correctly with my code and the markers are not shown on map.
const dummyGeoJson = {
type: "FeatureCollection",
features: [
{
type: "Feature",
properties: {},
geometry: {
type: "Point",
coordinates: [16.959285736083984, 52.40472293138462]
}
}
]
};
class EventMap extends React.Component {
componentDidMount() {
this.map = L.map("map", {
center: [51.9194, 19.1451],
zoom: 6
});
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
maxZoom: 20
}).addTo(this.map);
var geoJsonLayer = L.geoJSON().addTo(this.map);
geoJsonLayer.addData(dummyGeoJson);
}
render() {
return <Wrapper width="100%" height="800px" id="map" />;
}
}
From what i've read in official leaflet tutorial this code should create a new geojson layer and create a marker in a position referenced in geojson but actually the only thing that is shown is my tile layer.
You need to use a pointToLayer function in a GeoJSON options object when creating the GeoJSON layer like this:
componentDidMount() {
const map = L.map("map", {
center: [51.9194, 19.1451],
zoom: 6
});
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
maxZoom: 20
}).addTo(map);
L.geoJSON(dummyGeoJson, {
pointToLayer: (feature, latlng) => {
return L.marker(latlng, { icon: customMarker });
}
}).addTo(map);
}
You can then pass a customMarker variable to define some options in order to make your marker be displayed on the UI
Demo
Welcome to SO!
The most probable reason is that you bundle your app (typically with webpack), but the build misses Leaflet default icon images.
So your Marker is there, but you cannot see it because its icon image is missing.
An easy way to debug it is to use another icon instead, as suggested in kboul's answer, or even more simply by using a CircleMarker.
Then to solve the issue of the build engine missing to process the default icon images, see Leaflet #4968:
explicitly import / require the Leaflet default icon images and modify the L.Icon.Default options to use the new imported paths
or use the leaflet-defaulticon-compatibility plugin (I am the author).

Resources