I'm attempting to get zip codes to display within zip code borders - mapbox

Several moments are happening correctly in this effort, the map is displaying as expected, the zip code boundaries are showing as expected, but I'm not able to figure how to get 5-digit zip codes to be the label within each zip code boundary. Any help (with example code if possible) would be greatly appreciated!
Here's some code:
<html>
<div id='mapdiv'></div>
...
mapboxgl.accessToken='<token>';
var mapobj = new mapboxgl.Map({
container: 'mapdiv',
style: 'mapbox://styles/mapbox/streets-v9',
minZoom: 3,
maxZoom: 20,
zoom: 10,
center: [-105.1613858,39.6791558]
});
<script src="https://api.mapbox.com/mapbox-gl-js/v0.39.1/mapbox-gl.js"></script>
<link href="https://api.mapbox.com/mapbox-gl-js/v0.39.1/mapbox-gl.css" rel="stylesheet" />
...
mapobj.on('load', function() {
// Add ZipCode source to map
mapobj.addSource('zipSource', {
type: 'vector',
url: 'mapbox://mapbox.enterprise-boundaries-p2-v1'
});
mapobj.showTileBoundaries = true;
// Add hot ZipCode layer to map
mapobj.addLayer({
id: 'hotZipLayer',
type: 'fill',
source: 'zipSource',
'source-layer': 'boundaries_postal_2',
paint: {
'fill-outline-color': 'rgba(0,0,0,1)',
'fill-color': 'rgba(0,0,0,0.01)'
}
});
// Add Zip numbers symbol layer to map
mapobj.addLayer({
id: 'zipCodeLabels',
type: 'symbol',
source: 'zipSource',
'source-layer': 'points_postal_2',
layout: {
'text-field': '{id}',
'text-justify': 'center',
'text-size' : 10
},
paint: {
'text-color': 'black',
'text-halo-color': 'white',
'text-halo-width': 25
}
});
});
And an example data entry:
[
{
"geometry":
{
"type":"Point","coordinates":[-105.0908374786377,39.707747919880916]
},
"type":"Feature",
"properties":
{
"id":"USP280226"
},
"id":2,
"layer":
{
"id":"zipCodeLabels",
"type":"symbol",
"source":"zipSource",
"source-layer":"points_postal_2",
"layout":
{
"text-field":"{id}",
"text-justify":"center",
"text-size":10
},
"paint":
{
"text-color":"black",
"text-halo-color":"white",
"text-halo-width":25
}
}
},...]
So in this case the value that would show up within the zip code boundary is "USP280226", what I would like to appear is "80226", so I would like to call substring(4) on that id value, but I don't see an easy way to do that for each displayed zip code on the map.
I would imagine MapBox has a way to do this properly, but I haven't been able to find it in the docs or examples.
Thanks in advance for any help.

The currently released version of Mapbox-GL-JS doesn't support any kind of functions on data. You will have to process the data offline so that it contains the labels you want to display.
(I think a forthcoming version may support this kind of function, but I'm not certain.)
EDIT The "expression" functionality is now released. Unfortunately I don't think it helps you. There's a concat function but no way to split strings that I can see.

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

Update style of individual feature from single geoJSON source on Mapbox map, when clicked

I'm working with Mapbox GL JS to plot geoJSON data on a map using their external geoJSON example as a starting point. The geoJSON file contains lots of features which are plotted as individual markers on the same layer. I would like to highlight the clicked marker by changing its colour from red to blue. I have adapted the example to show a pop-up with the point id when clicked (just as a proof of concept that the markers can fire events when clicked), however, I'm struggling to find a way to change the styling of the individual clicked marker.
The code is currently as follows:
mapboxgl.accessToken = 'pk.eyJ1IjoiZGFuYnJhbWFsbCIsImEiOiJjbDB3ODFveHYxOG5rM2pubWpwZ2R1Y2xuIn0.yatzJHqBTjQ6F3DHASlriw';
const map = new mapboxgl.Map({
container: 'map', // container ID
style: 'mapbox://styles/mapbox/satellite-v9', // style URL
zoom: 7, // starting zoom
center: [138.043, 35.201] // starting center
});
map.on('load', () => {
map.addSource('earthquakes', {
type: 'geojson',
data: 'https://docs.mapbox.com/mapbox-gl-js/assets/earthquakes.geojson'
});
map.addLayer({
'id': 'earthquakes-layer',
'type': 'circle',
'source': 'earthquakes',
'paint': {
'circle-radius': 8,
'circle-stroke-width': 2,
'circle-color': 'red',
'circle-stroke-color': 'white'
}
});
});
map.on('click', 'earthquakes-layer', (e) => {
new mapboxgl.Popup()
.setLngLat(e.lngLat)
.setHTML('Id: ' + e.features[0].properties.id)
.addTo(map);
});
Here is a codepen: https://codepen.io/danb1/pen/BaYjOyx
Is it the case that it's actually not possible to use this approach, and instead each feature from the geoJSON file needs to be plotted as a separate layer? I'm struggling to find any examples of this and am not able to modify the geoJSON source — it has to come from one single file (rather than loading multiple geoJSON files separately on separate layers).
This is possible using feature-state. The first thing to do is to ensure the layer data contains ids for each feature (in the example the source data doesn't so we need to add generateId: true to the map.addSource method).
We then need to add mousemove and mouseleave events to the map to store the moused-over feature id (if there is one, i.e. if the mouse is hovering over a feature):
let hoveredEarthquakeId = null;
map.on('mousemove', 'earthquakes-layer', (e) => {
map.getCanvas().style.cursor = 'pointer';
if (e.features.length > 0) {
map.setFeatureState(
{ source: 'earthquakes', id: e.features[0].id },
{ hover: true }
);
hoveredEarthquakeId = e.features[0].id;
}
});
map.on('mouseleave', 'earthquakes-layer', () => {
map.getCanvas().style.cursor = '';
if (hoveredEarthquakeId !== null) {
map.setFeatureState(
{ source: 'earthquakes', id: hoveredEarthquakeId },
{ hover: false }
);
}
hoveredEarthquakeId = null;
});
Finally, in the layer properties, the colour setting of the circle needs to be updated to reflect the hover value stored against the feature:
'circle-color': [
'case',
['boolean', ['feature-state', 'hover'], false],
'#00f',
'#f00'
],
The final thing can be seen in the modified pen. There is also a MapBox tutorial covering this kind of thing in a slightly more complicated way, which I hadn't come across until now: https://docs.mapbox.com/help/tutorials/create-interactive-hover-effects-with-mapbox-gl-js/.

How to add Markers instead of Circles with csv2geojson data

I am importing data from a Google Spreadsheet with csv2geojson and it is drawing nice circles for each location with 'paint', I'd rather have wee pin markers though. What is the best way to achieve this?
function makeGeoJSON(csvData) {
csv2geojson.csv2geojson(csvData, {
latfield: 'Latitude',
lonfield: 'Longitude',
delimiter: ','
}, function (err, data) {
map.on('load', function () {
map.addLayer({
'id': 'csvData',
'type': 'circle',
'source': {
'type': 'geojson',
'data': data
},
'paint': {
'circle-radius': 10,
'circle-color': {
property: 'MarkerType',
type: 'categorical',
stops: [
['blue', '#fbb03b'],
['red', '#223b53'],
['pink', '#e55e5e']
]
}
}
});
If you would like to base the icons off of a category in your spreadsheet, you can do so one of two ways. One way to do so would be to make sure to add a column in your spreadsheet, define the name of the icon you'd like to use within the spreadsheet and upload the icons you would like to use in Mapbox Studio. Another way would be to set each category to a specific icon within your code. If you would like to add an image to your points, please take a look at this add an icon to the map example.
In addition, you will need to change your map.addLayer​ function to reference the appropriate icon. I have included my code below, as well as a screenshot of the final result for your reference. {Icon}references the icon property of my geojson (converted from the Google sheets CSV file), which came from the column in my spreadsheet.
My map.addLayer code:
map.addLayer({
'id': 'csvData',
'type': 'symbol',
'source': {
'type': 'geojson',
'data': data
},
'layout': {
'icon-image': '{Icon}',
'icon-size': 1.5,
'icon-padding': 0,
'icon-allow-overlap': true
}
});

mvt vector tiles on showing on MapboxGL

I followed
https://medium.com/#mrgrantanderson/https-medium-com-serving-vector-tiles-from-django-38c705f6
to serve the mvt tiles to the mapbox from geoDjango.
With running query
cursor.execute("SELECT ST_AsMVT(tile) FROM (SELECT osm_id, building, ST_AsMVTGeom(geom, TileBBox(%s, %s, %s, 3857)) FROM nepal_khokanabuildings ) AS tile", [zoom, x, y])
as my model project is ESPG:3857
The Vectors don't seem to load up on the map, the api request is working fine.
I also tried serving vector files from Geoserver no luck either.
Here is my JS File
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/light-v10',
zoom: 12,
center: [85.294688,27.634106],
});
var mapillarySource = {
type: 'vector',
tiles: [
'http://0.0.0.0:8000/nepal/api/v1/data/nepal/{z}/{x}/{y}.mvt'
],
minzoom: 0,
maxzoom: 14
};
map.on('load', function() {
map.addSource('mapillary', mapillarySource);
map.addLayer({
'id': 'mapillary',
'type': 'fill',
'source': 'mapillary',
'source-layer': 'water',
'paint': {
"fill-color": "#00ffff"
}
});
});
map.addControl(new mapboxgl.NavigationControl());
</script>
There are lots of problems that can manifest themselves as "my layers don't show".
You can check each of these things:
is the layer being created before the map is ready? (wait for "load" event)
are the correct tile requests being generated?
are those requests succeeding?
are they returning actual .pbf files?
do they contain data in the right location, and in the right projection?
do they contain a layer with the name you expect? ('water' in this case)
do they contain data of the type you expect? (polygons in this case)
I am curious about the 0.0.0.0 host, but also suspect that the layer name may not be right.
If your tile requests are succeeding, you can try using https://stevage.github.io/vector-inspector/ to inspect them, although you may have issues with that page being served on HTTPS and your local tiles being on HTTP.
Use Following ,
"id": "postgis-tiles-layer",
"source-layer": "default",

mapbox-gl-js create a circle around a lat/lng?

I need to create a circle around a point where a user clicks. How would I do this? Every tutorial shows extracting a circle from a geojson source and not creating one. Need to be able to edit the radius as well.
Did you try something yourself? Following the mapbox examples you should be able to get an idea of how to build something like that.
You would need to do 3 things:
Create a source that holds the data
Create a layer of type "circle" for displaying the data as circles
On every click of the user, extract the "latitude, longitude" and add a point to your data list. Then display all of those points as a circle on the map.
This is an example of how I would have coded that: https://jsfiddle.net/andi_lo/495t0dx2/
Hope that helps you out
mapboxgl.accessToken = '####';
var map = new mapboxgl.Map({
container: 'map', // container id
style: 'mapbox://styles/mapbox/light-v9', //stylesheet location
center: [-74.50, 40], // starting position
zoom: 9 // starting zoom
});
map.on('load', () => {
const points = turf.featureCollection([]);
// add data source to hold our data we want to display
map.addSource('circleData', {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: [],
},
});
// add a layer that displays the data
map.addLayer({
id: 'data',
type: 'circle',
source: 'circleData',
paint: {
'circle-color': '#00b7bf',
'circle-radius': 8,
'circle-stroke-width': 1,
'circle-stroke-color': '#333',
},
});
// on user click, extract the latitude / longitude, update our data source and display it on our map
map.on('click', (clickEvent) => {
const lngLat = new Array(clickEvent.lngLat.lng, clickEvent.lngLat.lat);
points.features.push(turf.point(lngLat));
map.getSource('circleData').setData(points);
});
});
#map {
height: 500px;
}
<div id="map"></div>