How to filter features from coordinates - reactjs

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
}

Related

Connect markers with a polyline in Mapbox GL

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

How do I select and update an object from a larger group of objects in Recoil?

My situation is the following:
I have an array of game objects stored as an atom, each game in the array is of the same type and structure.
I have another atom which allows me to store the id of a game in the array that has been "targeted".
I have a selector which I can use to get the targeted game object by searching the array for a match between the game ids and the targeted game id I have stored.
Elsewhere in the application the game is rendered as a DOM element and calculations are made which I want to use to update the data in the game object in the global state.
It's this last step that's throwing me off. Should my selector be writable so I can update the game object? How do I do this?
This is a rough outline of the code I have:
export const gamesAtom = atom<GameData[]>({
key: 'games',
default: [
{
id: 1,
name: 'Bingo',
difficulty: 'easy',
},
{
id: 21,
name: 'Yahtzee',
difficulty: 'moderate',
},
{
id: 3,
name: 'Twister',
difficulty: 'hard',
},
],
});
export const targetGameIdAtom = atom<number | null>({
key: 'targetGameId',
default: null,
});
export const targetGameSelector = selector<GameData | undefined>({
key: 'targetGame',
get: ({ get }) => {
return get(gamesAtom).find(
(game: GameData) => game.id === get(selectedGameIdAtom)
);
},
// This is where I'm getting tripped up. Is this the place to do this? What would I write in here?
set: ({ set, get }, newValue) => {},
});
// Elsewhere in the application the data for the targetGame is pulled down and new values are provided for it. For example, perhaps I want to change the difficulty of Twister to "extreme" by sending up the newValue of {...targetGame, difficulty: 'extreme'}
Any help or being pointed in the right direction will be appreciated. Thanks!

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

Creating an array from GeoJSON file in OpenLayers 3

I am using OpenLayers 3 to animate the paths of migrating animals tagged by scientists. I load the geoJSON file like so
var whaleSource = new ol.source.Vector({
url: 'data/BW2205005.json',
format: new ol.format.GeoJSON()
});
Instead of loading this directly into a layer, I would like to use and reuse the data in the geoJSON file for different purposes throughout my program. For example, I want to pull the lat & lon coordinates into an array to manipulate them to create interpolated animated tracks. Later I will want to query the geoJSON properties to restyle the tracks of males and females.
How might I load the geoJSON data into various arrays at different stages of my program instead of directly into a layer?
Thanks much
When using the url property of ol.source.Vector the class loads the given url via XHR/AJAX for you:
Setting this option instructs the source to use an XHR loader (see ol.featureloader.xhr) and an ol.loadingstrategy.all for a one-off download of all features from that URL.
You could load the file yourself using XHR/AJAX using XMLHttpRequest or a library like jquery which has XHR/AJAX functionality. When you've retreived the GeoJSON collection you can loop over the features array it holds and split it up into what every you need and put those features into new separate GeoJSON collections. Here's a very crude example to give you and idea of the concept:
Assume the following GeoJSON collection:
{
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [0, 0]
},
"properties": {
"name": "Free Willy"
}
}, {
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [1, 1]
},
"properties": {
"name": "Moby Dick"
}
}, {
// Etc.
}]
}
Here's how to load it (using jQuery's $.getJSON XHR function) and to split it up in to separate collections:
// Object to store separated collections
var whales = {};
// Load url and execute handler function
$.getJSON('collection.json', function (data) {
// Iterate the features of the collection
data.features.forEach(function (feature) {
// Check there is a whale already with that name
if (!whales.hasOwnProperty(feature.properties.name)) {
// No there isn't create empty collection
whales[feature.properties.name] = {
"type": "FeatureCollection",
"features": []
};
}
// Add the feature to the collection
whales[feature.properties.name].features.push(feature);
});
});
Now you can use the separate collections stored in the whale object to create layers. Note this differs some from using the url property:
new ol.layer.Vector({
source: new ol.source.Vector({
features: (new ol.format.GeoJSON()).readFeatures(whales['Free Willy'], {
featureProjection: 'EPSG:3857'
})
})
});
Here's a working example of the concept: http://plnkr.co/edit/rGwhI9vpu8ZYfAWvBZZr?p=preview
Edit after comment:
If you want all the coordinates for Willy:
// Empty array to store coordinates
var willysCoordinates = [];
// Iterate over Willy's features
whales['Free Willy'].features.forEach(function (feature) {
willysCoordinates.push(feature.geometry.coordinates);
});
Now willysCoordinates holds a nested array of coordinates: [[0, 0],[2, 2]]

Resources