How do I format the geojson data so that it is accepted by Mapbox - mapbox

I have a geoJson of hex indexes, which I am trying to render on to mapbox.
Here is my code (ignore the indentation):
export default class App extends Component {
constructor() {
super();
const hexagons = ['8928308280fffff', '8928308281fffff', '8928308282fffff'];
console.log(hex)
this.state = {
route:
{
type: 'FeatureCollection',
features: hexagons.map(hex => ({
type: 'Feature',
geometry: {
type: 'Polygon',
coordinates: h3ToGeoBoundary(hex, { resolution: 8 })
}
}))
},
}
}
render() {
return (
<View style={styles.container}>
<MapboxGL.MapView
styleURL={MapboxGL.StyleURL.Light}
zoomLevel={15}
centerCoordinate={[11.256, 43.770]}
style={styles.container}>
<MapboxGL.ShapeSource id='hexagons' shape={this.state.route}>
<MapboxGL.FillExtrusionLayer id='hexagons' style={{ fillExtrusionColor: '#ff0000' }} />
</MapboxGL.ShapeSource>
</MapboxGL.MapView>
</View>
);
}
}
This is how the geojson looks when converting from hexagons[0]:
[[68.92995788193984, 31.831280499087402], [69.3935964899183, 62.345344956509784], [76.163042830191, 94.14309010184775], [87.36469532319619, 145.5581976913369], [81.27137179020501, -34.75841798028471], [73.31022368544396, 0.32561035194326043]]
This is the error I am getting:
Mapbox error RCTMGLShapeSource.updateShape The data couldn’t be read because it isn’t in the correct format. {"level": "error", "message": "RCTMGLShapeSource.updateShape The data couldn’t be read because it isn’t in the correct format."}
Mapbox error Unable to read shape: {"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[-122.41719971841654,37.77519778289337],...dataCorrupted(Swift.DecodingError.Context(codingPath: [], debugDescription: \"Failed to decode GeoJSONSource `data` property\", underlyingError: nil)) setting it to empty"}
Any suggestions?

There is an error in the H3 call here, but I don't actually think it's the problem. The function h3ToGeoBoundary takes a cell (which has a fixed resolution), and a boolean formatAsGeoJSON argument (see docs). Your call h3ToGeoBoundary(hex, { resolution: 8 }) is accidentally giving you GeoJSON, because the { resolution: 8 } argument is interpreted as truthy.
But that doesn't seem to be the issue here, as you get GeoJSON out anyway. I can't debug the issue here without a better error; a very strict GeoJSON parser might require a properties key in each Feature, but that seems unlikely. Note that you can also use the geojson2h3 library to simplify the conversion from H3 to GeoJSON somewhat, though I can't guarantee that it will fix your problem in this case.

Related

Mapbox GL JS Change the fill-color's property value based on an expression

I have a chloropleth map with a geojson data source backing it. The data set contains data for each country for two different years, with the fill color based on one of the properties in the JSON. Here is my addLayer code currently, which works fine.
map.addLayer({
id: 'emissions',
type: 'fill',
source: {
type: 'geojson',
data: './data.json'
},
paint: {
'fill-color': {
property: 'total_2014',
type: 'exponential',
base: 0.99999,
stops: [
[3, "hsl(114, 66%, 53%)"],
[2806634, "hsl(0, 64%, 51%)"]
]
},
'fill-opacity': 1
}
});
I would like to be able to programatically switch the json property on which the fill color is based, and an expression seems the obvious way to do so, however the following code fails with the error layers.emissions.paint.fill-color.property: string expected, array found.
...
paint: {
'fill-color': {
property: ['get', propName], // propName var is e.g. 'total_2014'
type: 'exponential',
base: 0.99999,
stops: [
[3, "hsl(114, 66%, 53%)"],
[2806634, "hsl(0, 64%, 51%)"]
]
},
'fill-opacity': 1
}
...
Is there a way to achieve what I'm aiming for? I'm very new to using Mapbox GL JS, so apologies if this is a basic question.
Just in case anyone else happens across this requirement, I found a workaround by updating the map's style property directly. This isn't an exact answer since the approach doesn't use expressions, but the performance of mapbox diffing source and applying the changes is very fast and meets my requirements.
function loadDataForYear(year) {
const style = map.getStyle();
style.layers.find(({ id }) => id === "emissions").paint['fill-color']['property'] = 'total_' + year;
map.setStyle(style);
}

How to Stop Markers Moving on Zoom in Mapbox GL JS

I have tried previous answers with no success.
On initial load custom markers are in the correct position, though I do have to manually shift them to the right by 220px to achieve this. There is no zoom level set due to the fitBounds we use, described in the next paragraph.
On zooming in or out, the markers lose their position - their position is only correct for a certain zoom level. I am using a Turf.js bbox to calculate the bounding box we can use in the fitBounds function (though this is slightly out with me having to shift the markers to the right 220px).
In my _app.js (Next.js) I import the css
import 'mapbox-gl/dist/mapbox-gl.css'
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
export default MyApp
This is my Map component
import React, { useRef, useEffect } from 'react'
import mapboxgl from 'mapbox-gl'
import * as turf from '#turf/turf'
mapboxgl.accessToken = process.env.NEXT_PUBLIC_MAPBOX_TOKEN
export default function App() {
const mapContainer = useRef(null)
const map = useRef(null)
var geojson = {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
properties: { price: 399, beds: 2 },
geometry: { type: 'Point', coordinates: [115.1848543, -8.721661] },
},
{
type: 'Feature',
properties: { price: 450, beds: 3 },
geometry: { type: 'Point', coordinates: [115.1676773, -8.72259] },
},
],
}
var bbox = turf.bbox(geojson)
useEffect(() => {
if (map.current) return // initialize map only once
map.current = new mapboxgl.Map({
container: mapContainer.current,
style: 'mapbox://styles/mapbox/streets-v11',
})
for (const marker of geojson.features) {
// Create a DOM element for each marker.
const el = document.createElement('div')
//? WE USE ml-[220px] AS THE MARKERS APPEAR 220PX TO THE LEFT OF WHERE THEY SHOULD BE
el.className =
'flex items-center justify-center h-[30px] px-3 tracking-wide font-extrabold rounded-full bg-zinc-100 border border-zinc-300 shadow-md text-[14px] text-black ml-[220px]'
el.innerText = '$' + marker.properties.price
// Add markers to the map.
new mapboxgl.Marker(el).setLngLat(marker.geometry.coordinates).addTo(map.current)
}
map.current.fitBounds(bbox, { padding: 100 })
})
return <div ref={mapContainer} className='w-full h-screen' />
An example can be found here
Any help would be very much appreciated.
On initial load custom markers are in the correct position, though I do have to manually shift them to the right by 220px to achieve this.
This doesn't make sense. If you have to move them 220px, they're clearly not in the right position. Presumably, the longitude of the point is wrong, and by offsetting it 220px it temporarily looks right - but as soon as the zoom changes, it will be wrong by a different amount.
You need to:
get the right lat/longs for your markers
make sure your marker images don't contain extra white space
set icon-anchor correctly for your image design

Mapbox GL v.0.54: Change icon-image based on click

I'm trying to implement a single-layer in mapbox-GL that shows icons which change when clicked. So far I've attempted the following:
Use a property (after pre-loading the images as active and inactive):
'icon-image': ["case",["boolean", ["feature-state", "clicked"], false],
"inactive",
"active"
],
And various versions of map.updateImage() to dynamically change the image that is displayed for each point:
map.updateImage(`point1`, map.loadImage('/img/pin_active.png'))
map.updateImage(`point1`, 'active')) //with the image being loaded beforehand as 'active'
map.updateImage(`point1`, map.loadImage('/img/pin_active.png', function(error, image) {
if (error) throw error;
map.addImage(point1, image);
}))
The only solution that does work is using SDF (mapbox gl change icon color) - but this does not work for my icons, which are multi-color (and they get pretty ugly since the SDF format seems to scale badly).
Any ideas on how to approach this?
Ok, so after a bit of extra fiddling around I found a working solution. Just leaving this here for whoever finds it later.
If we load the images beforehand as strings active and inactive:
map.loadImage('/img/pin_blue.png', function(error, image) {
if (error) throw error;
map.addImage('inactive', image);
})
map.loadImage('/img/pin_red.png', function(error, image) {
if (error) throw error;
map.addImage('active', image);
})
we can do the following:
let data = {} // object that stores the geojson data
let points = map.queryRenderedFeatures(e.point, {
layers: ['projects-point']
})
if (points.length) {
data.map(function(item, index) {
if (item.id === points[0].id) {
data[index].properties.image = 'active'
}
else
data[index].properties.image = 'inactive'
})
map.getSource('projects').setData(this.dataObj.data)
}

I can't get the markers to show up on the map(React-leaflet.js)

I got the "lat" and "lng" of all the Bike Points from the TFL's API. I am trying to show all the Bike Points as markers on the React-leaflet map.
I have successfully got the data and have filtered it to the right format [51.505, -0.09] and mapping each one on to the Marker
component <Marker position={data.position} /> .
The problem is the markers not showing up on the map.
I have hard coded some data and it works so I don't understand what I am doing wrong with the live data. I will be very grateful if someone could help me? I have been stuck on this for all night.
Here is my problem in Jsfiddle:
Problem on jsfiddle!
There is a typo here, you need to return element (refer Lists and Keys docs for a more details):
{this.state.bikeMarkers.map(data => {
return <Marker position={data.position}></Marker>
^^^^^^
missing
})}
filterData function looks redundant in the provided example, data could be retrieved and filtered in the first place like this:
axios.get(`https://api.tfl.gov.uk/bikepoint`).then(res => {
let markers = res.data.map(location => {
return { key: location.id, position: [location.lat, location.lon] };
});
this.setState({
bikeMarkers: markers
});
});
Updated jsFiddle

How to use clusterProperties in mapbox-gl

I built a mapbox-gl js (v0.52) map where points are getting aggregated into clusters; much like in the clusters example from mapbox page.
The cluster color needs to be a function of an aggregation of individual points properties: For simplicity, say each point has a status property, which determine its color, and the color of a cluster should just be the color corresponding to the max of each of its points' status values.
Example geojson data would look like:
const geoData = {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
properties: {
id: 'fakeid11',
status: 20,
},
geometry: {
type: 'Point',
coordinates: [-151.5129, 63.1016, 0]
}
},
{
type: 'Feature',
properties: {
id: 'fakeid22',
status: 10,
},
geometry: {
type: 'Point',
coordinates: [-150.4048, 63.1224, 105.5]
}
}
]
};
I am trying to use clusterProperties to compute the aggregation as described in the api docs, similar to this example from the source code, to create this layer:
map.addSource('earthquakes', {
type: 'geojson',
data: geoData,
cluster: true,
clusterMaxZoom: 14, // Max zoom to cluster points on
clusterRadius: 50, // Radius of each cluster when clustering points (defaults to 50)
clusterProperties: {
status: ['max', ['get', 'status']]
}
});
This snippet is exactly like the clusters example from mapbox page, just replacing the data by my static 2-elements copy, and adding clusterProperties.
However this throws a validation error (a bit mangled from the minified mapbox-gl version):
Error: sources.earthquakes: unknown property "clusterProperties"
at Object.Jr [as emitValidationErrors] (modules.js?hash=34588b9e7240c1a9b3fd2f8685e299d9dbbb40d9:504146)
at De (modules.js?hash=34588b9e7240c1a9b3fd2f8685e299d9dbbb40d9:504150)
at i._validate (modules.js?hash=34588b9e7240c1a9b3fd2f8685e299d9dbbb40d9:504150)
What is wrong with this clusterProperties usage? It would seem it is simply refusing to validate this property. Note that the map works ok (without the status aggregated property of course) if I comment the last 3 lines that set it.
I found the answer days later: The version of react-map-gl we were using had dependency on mapbox-gl ~0.52.0, which did not yet support clusterProperties. Support for these aggregated properties comes in mapbox-gl 0.53. (And since the react wrapper uses undocumented features of mapbox-gl, they depend on exact versions at patch level). This was confirmed by the react library developers.
Now react-map-gl 4.0.14 is released and it works ok with clusterProperties, using internally mapbox-gl 0.53.0.