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

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

Related

React : Async - Select drop down - JSON object trouble

I am using the Async - React Select drop down in react - https://react-select.com/async
The dataset for the drop down needs to look like this :
async function getPromise4(): Promise<Person[]> {
return [
{
value: 'Tom',
label: 'Tom',
},
{
value: 'David',
label: 'David',
},
{
value: 'MeeMee',
label: 'MeeMee',
},
];
}
and I am trying to get this created from an API. The API works ok but it doesn't give it back as value / label - so I cannot return the data direct to the drop down by a promise.
I am struggling to create an object like about. This is the code I use to get the data from the API.
async function main(): Promise<void> {
const foo = await dynamicsWebApi.retrieveAll("accounts",["name"]).then(function (stuff) {
var records = stuff.value
records.forEach(function (value) {
console.log(value.name);
This above code gets the data from the API fine. I guess I need to iterate over the data and build an object like above. I am sure its quite reasonable to do but I cannot seem to figure it out.

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!

Formatting data from a database in TypeScript

I am having trouble with writing the following method on an Angular class. I don't know how to add values from arrayId to the data array in the series object.
getChartOptions() {
const arrayId=[];
const arrayTimestamp=[];
const arrayData=[];
const arrayData2=[];
var i=0;
this.httpClient.get<any>('http://prod.kaisens.fr:811/api/sleep/?deviceid=93debd97-6564-454b-be33-35bd377a2563&startdate=1612310400000&enddate=1614729600000').subscribe(
reponse => {
this.sleeps = reponse;
this.sleeps.forEach(element => { arrayId.push(this.sleeps[i]._id),arrayTimestamp.push(this.sleeps[i].timestamp),arrayData.push(this.sleeps[i].data[18]),arrayData2.push(this.sleeps[i].data[39])
i++;
});
console.log(arrayId);
console.log(arrayTimestamp);
console.log(arrayData);
console.log(arrayData2);
}
)
return {
series: [{
name: 'Id',
data: [35, 65, 75, 55, 45, 60, 55]
}]
}
}
I have two main pieces of advice for you:
Know the types of that data that you are dealing with.
Get familiar with all of the various array methods.
get<any>() is not a helpful type. If you understand what the response is then Typescript can help ensure that you are handling it correctly.
I checked out the URL and it looks like you get an array of objects like this:
{
"_id": 4,
"device_id": "93debd97-6564-454b-be33-35bd377a2563",
"timestamp": 1612310400000.0,
"data": "{'sleep_quality': 1, 'sleep_duration': 9}"
},
That data property is not properly encoded as an object or as a parseable JSON string. If you control this backend then you will want to fix that.
At first I thought that the data[18] and data[39] in your code were mistakes. Now I see that it as attempt to extract values from this malformed data. Accessing by index won't work if these numbers can be 10 or more.
The type that you have now is:
interface DataPoint {
_id: number;
device_id: string;
timestamp: number;
data: string;
}
The type that you want is:
interface DataPoint {
_id: number;
device_id: string;
timestamp: number;
data: {
sleep_quality: number;
sleep_duration: number;
}
}
You can type the request as this.httpClient.get<DataPoint[]>( and now you'll get autocomplete on the data.
It looks like what you are trying to do is basically to convert this from one array of rows to a separate array for each column.
You do not need the variable i because the .forEach loop handles the iteration. The element variable in the callback is the row that you want.
this.sleeps.forEach(element => {
arrayId.push(element._id);
arrayTimestamp.push(element.timestamp);
arrayData.push(element.data[18]);
arrayData2.push(element.data[39]);
});
The .forEach loop that you have now is efficient because it only loops through the array once. A .map for each column is technically less efficient because we have to loop through separately for each column, but I think it might make the code easier to read and understand. It also allows Typescript to infer the types of the arrays. Whereas with an empty array you would need to annotate it like const arrayId: number[] = [];.
const mapData = (response: DataPoint[]) => {
return [{
name: 'Id',
data: response.map(element => element._id)
}, {
name: 'Timestamp',
data: response.map(element => element.timestamp)
}, {
name: 'Sleep Quality',
data: response.map(element => parseInt(element.data[18])) // fix this
}, {
name: 'Sleep Duration',
data: response.map(element => parseInt(element.data[39])) // fix this
}]
}
The HTTP request is asynchronous. If you access your array outside of the subscribe callback then they are still empty. I'm not an angular person so this part I'm unsure of, but I think that you want to be updating a property on your class instead of returning the value?
Just follow this piece of code:
series: [{
name: 'Id',
data: arrayId
}]

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.

Resources