Adding multiple sources and layers with .each? - mapbox

I try to simplify my codebase for an ongoing project. I render different lines from different sources. Adding the source and layer one by one works as expected. But if I put this in an object it won't work. I even have no errors in the console. Now I am stuck at the following code
var trip_entries = {
t2: {
trip_id: 'trip-entry-2',
trip_geojson: '[[-0.15591514, 51.51830379],[-0.07571203, 51.51424049],[-0.08533793, 51.50438536],[-0.085793, 51.5036],[-0.084793, 51.503336],[-0.089793, 51.505336]]'
},
t3: {
trip_id: 'trip-entry-3',
trip_geojson: '[[-0.15514, 51.518],[-0.075703, 51.515],[-0.085793, 51.50],[-0.0793, 51.506],[-0.08473, 51.50336],[-0.0893, 51.536]]'
},
};
// Set route
$.each(trip_entries,function(key,value){
// Add sources
map.addSource(value.trip_id,{
'type': 'geojson',
'data': {
'type': 'FeatureCollection',
'features': [
{
'type': 'Feature',
'geometry': {
'type': 'LineString',
'coordinates': value.trip_geojson
}
},
],
},
});
// Add layers
map.addLayer({
'id': value.trip_id,
'type': 'line',
'source': value.trip_id,
'layout': {
'line-join': 'round',
'line-cap': 'round'
},
'paint': {
'line-color': 'rgb(160,160,160)',
'line-width': 6,
}
});
});

You are passing the geoJSON as a string, rather than an array of coordinates. You should remove the ' ' on both of your geoJSONs to get the code to run. See below:
mapboxgl.accessToken =
"pk.eyJ1IjoicGxtYXBib3giLCJhIjoiY2s3MHkzZ3VnMDFlbDNmbzNiajN5dm9lOCJ9.nbbtDF54HIXo0mCiekVxng";
var map = new mapboxgl.Map({
container: "map", // container id
style: "mapbox://styles/mapbox/streets-v11", // stylesheet location
center: [-0.084793, 51.503336], // starting position [lng, lat]
zoom: 11 // starting zoom
});
var trip_entries = {
t2: {
trip_id: "trip-entry-2",
trip_geojson: [
[-0.15591514, 51.51830379],
[-0.07571203, 51.51424049],
[-0.08533793, 51.50438536],
[-0.085793, 51.5036],
[-0.084793, 51.503336],
[-0.089793, 51.505336]
]
},
t3: {
trip_id: "trip-entry-3",
trip_geojson: [
[-0.15514, 51.518],
[-0.075703, 51.515],
[-0.085793, 51.5],
[-0.0793, 51.506],
[-0.08473, 51.50336],
[-0.0893, 51.536]
]
}
};
map.on("load", function () {
// Set route
$.each(trip_entries, function (key, value) {
// Add sources
map.addSource(value.trip_id, {
type: "geojson",
data: {
type: "FeatureCollection",
features: [
{
type: "Feature",
geometry: {
type: "LineString",
coordinates: value.trip_geojson
}
}
]
}
});
// Add layers
map.addLayer({
id: value.trip_id,
type: "line",
source: value.trip_id,
layout: {
"line-join": "round",
"line-cap": "round"
},
paint: {
"line-color": "rgb(160,160,160)",
"line-width": 6
}
});
});
});
body {
margin: 0;
padding: 0;
}
#map {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://api.mapbox.com/mapbox-gl-js/v1.9.1/mapbox-gl.js"></script>
<link href="https://api.mapbox.com/mapbox-gl-js/v1.9.1/mapbox-gl.css" rel="stylesheet" />
<div id="map"></div>

Related

How to change Mapbox map coordinates after button click

I have a page with a button and a map with a line that is drawn from a GeoJSON source. I want that when the user clicks on the button, the coordinates of the map change. The button itself is outside the map. Help me figure out how to connect this event to the button.
Here is my code which I thought should work. But this doesn't work:
<body>
<div>
<button type="button" id="sender">Send data</button>
<div id="map" class="col-md-9 ms-sm-auto col-lg-9 px-md-4"></div>
</div>
<script>
mapboxgl.accessToken = 'pk.eyJ1IjoibGFnZXJ0cmlwIiwiYSI6ImNsYWthb3gyYzBrYjAzb3FodGNqczBodGoifQ.ZN2ufFAb1kYidqr_eEE-bA';
const map = new mapboxgl.Map({
container: 'map',
// Choose from Mapbox's core styles, or make your own style with Mapbox Studio
style: 'mapbox://styles/mapbox/dark-v11',
center: [-122.486052, 37.830348],
zoom: 14
});
map.on('load', () => {
map.addSource('route', {
'type': 'geojson',
'data': {
'type': 'Feature',
'properties': {},
'geometry': {
'type': 'LineString',
'coordinates': [
[-122.483696, 37.833818],
[-122.483482, 37.833174],
[-122.483396, 37.8327],
[-122.483568, 37.832056],
[-122.48404, 37.831141],
[-122.48404, 37.830497],
[-122.483482, 37.82992],
[-122.483568, 37.829548],
[-122.48507, 37.829446],
[-122.4861, 37.828802],
[-122.486958, 37.82931],
[-122.487001, 37.830802],
[-122.487516, 37.831683],
[-122.488031, 37.832158],
[-122.488889, 37.832971],
[-122.489876, 37.832632],
[-122.490434, 37.832937],
[-122.49125, 37.832429],
[-122.491636, 37.832564],
[-122.492237, 37.833378],
[-122.493782, 37.833683]
]
}
}
});
map.addLayer({
'id': 'route',
'type': 'line',
'source': 'route',
'layout': {
'line-join': 'round',
'line-cap': 'round'
},
'paint': {
'line-color': '#888',
'line-width': 8
}
});
map.setData();
});
const btn = document.getElementById('sender')
btn.onclick = () => {
map.setData('route', {
'type': 'geojson',
'data': {
'type': 'Feature',
'properties': {},
'geometry': {
'type': 'LineString',
'coordinates': [
[-122.483482, 37.82992],
[-122.483568, 37.829548],
[-122.48507, 37.829446],
[-122.4861, 37.828802],
[-122.486958, 37.82931],
[-122.487001, 37.830802],
[-122.487516, 37.831683],
[-122.488031, 37.832158],
[-122.488889, 37.832971],
]
}
}
});
map.addLayer({
'id': 'route',
'type': 'line',
'source': 'route',
'layout': {
'line-join': 'round',
'line-cap': 'round'
},
'paint': {
'line-color': '#888',
'line-width': 8
}
});
}
</script>
</body>
if I understand your question then you can try this :
remove the existing route
then add the second layer to the map,"I remove the source also to avoid any error if user click the button twice,and you can use the same variable 'route' again"
something like this
<body>
<div>
<button type="button" id="sender">Send data</button>
<div id="map" class="col-md-9 ms-sm-auto col-lg-9 px-md-4"></div>
</div>
<script>
mapboxgl.accessToken = 'pk.eyJ1IjoibGFnZXJ0cmlwIiwiYSI6ImNsYWthb3gyYzBrYjAzb3FodGNqczBodGoifQ.ZN2ufFAb1kYidqr_eEE-bA';
const map = new mapboxgl.Map({
container: 'map',
// Choose from Mapbox's core styles, or make your own style with Mapbox Studio
style: 'mapbox://styles/mapbox/dark-v11',
center: [-122.486052, 37.830348],
zoom: 14
});
map.on('load', () => {
map.addSource('route', {
'type': 'geojson',
'data': {
'type': 'Feature',
'properties': {},
'geometry': {
'type': 'LineString',
'coordinates': [
[-122.483696, 37.833818],
[-122.483482, 37.833174],
[-122.483396, 37.8327],
[-122.483568, 37.832056],
[-122.48404, 37.831141],
[-122.48404, 37.830497],
[-122.483482, 37.82992],
[-122.483568, 37.829548],
[-122.48507, 37.829446],
[-122.4861, 37.828802],
[-122.486958, 37.82931],
[-122.487001, 37.830802],
[-122.487516, 37.831683],
[-122.488031, 37.832158],
[-122.488889, 37.832971],
[-122.489876, 37.832632],
[-122.490434, 37.832937],
[-122.49125, 37.832429],
[-122.491636, 37.832564],
[-122.492237, 37.833378],
[-122.493782, 37.833683]
]
}
}
});
map.addLayer({
'id': 'route',
'type': 'line',
'source': 'route',
'layout': {
'line-join': 'round',
'line-cap': 'round'
},
'paint': {
'line-color': '#888',
'line-width': 8
}
});
//map.setData();
});
const btn = document.getElementById('sender')
btn.onclick = () => {
map.removeLayer('route');
map.removeSource('route')
var new_route = {
type: 'Feature',
properties: {},
geometry: {
type: 'LineString',
'coordinates': [
[-122.483482, 37.82992],
[-122.483568, 37.829548],
[-122.48507, 37.829446],
[-122.4861, 37.828802],
[-122.486958, 37.82931],
[-122.487001, 37.830802],
[-122.487516, 37.831683],
[-122.488031, 37.832158],
[-122.488889, 37.832971],
]
},
}
map.addLayer({
id: 'route',
type: 'line',
source: {
type: 'geojson',
data: new_route,
},
layout: {
'line-join': 'round',
'line-cap': 'round',
},
paint: {
'line-color': '#888',
'line-width': 8,
},
})
}
</script>
</body>
working fiddle
The Mapbox documentation turned out to be an interesting method that helped with my problem. I found a similar answer in another post, so I'm posting what I think is the best solution:
btn.addEventListener('click', function () {
map.getSource('data-update').setData(geoJSONobj);
})
Or you can pass an object in which to put your parameters and even center the map to where the route was built:
btn.addEventListener('click', function () {
map.getSource('data-update').setData({
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": custom_coordinates,
}
}]
});
map.flyTo({
center: custom_coordinates[0],
speed: 3,
})
})

How to have two mapbox raster layers with different opacities?

Im using Mapbox GL API, and I run into the issue that if I add 2 tile layers, that the opacity of the second layer in the paint object is ignored. Does anyone have any idea why this is? In the browser both tile layers have opacity 1.
let style1 = {
id: "source1-tile",
type: "raster",
source: "source1",
paint: {
"raster-opacity": 1.0
},
}
this.map.addLayer(style1);
let style2 = {
id: "source2-tile",
type: "raster",
source: "source2",
paint: {
"raster-opacity": 0.5
},
}
this.map.addLayer(style2);
// print result
console.log(this.map.getStyle().layers)
// this shows the following:
/*
[
{
id: "source1-tile"
paint: Object { "raster-opacity": 1 }
source: "source1"
type: "raster"
},
{
id: "source2-tile"
source: "source2"
type: "raster"
}
]
*/
Pay attention to add layers in map.load event. I've made this example based on mapbox-gl examples. Easily You could add more raster layers with different opacity.
mapboxgl.accessToken = 'pk.eyJ1IjoibHN0aXoiLCJhIjoiY2s5dGtnNTZ2MWVybzNobjEyam0yd2E3MyJ9.6dCvGbS93SKGMbOqZA4Qag';
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v12',
center: [-87.62, 41.86],
zoom: 9
});
map.on('load', () => {
map.addSource('chicago', {
'type': 'raster',
'url': 'mapbox://mapbox.u8yyzaor'
});
map.addLayer({
'id': 'chicago',
'source': 'chicago',
'type': 'raster'
});
map.setPaintProperty(
'chicago',
'raster-opacity',
0.5
);
});

How to use react-map-gl to draw line between two point

I am trying to draw a line between two points using react-map-gl library. I can not find example from the official document, So I am trying to reproduce same behavior from following code snippet which use Mapbox library
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v11',
center: [-122.486052, 37.830348],
zoom: 15
});
map.on('load', function() {
map.addSource('route', {
'type': 'geojson',
'data': {
'type': 'Feature',
'properties': {},
'geometry': {
'type': 'LineString',
'coordinates': [
[-122.483696, 37.833818],
[-122.493782, 37.833683]
]
}
}
});
map.addLayer({
'id': 'route',
'type': 'line',
'source': 'route',
'layout': {
'line-join': 'round',
'line-cap': 'round'
},
'paint': {
'line-color': '#888',
'line-width': 8
}
});
});
Here is the sandbox, I do not see any errors on the console but the line is not displayed:
https://codesandbox.io/s/draw-line-between-two-point-v0mbc?file=/src/index.js:214-226
The code in the sandbox actually works (for me anyway), but is misleading because the line drawn is nowhere near the viewport.
A couple of things to note are that coordinates are an array given in [long, lat] which may not be what most people would assume. For example, if you cut and paste [lat,long] from google maps for San Fransisco, you get [37.77909036739809, -122.41510269913951]. Then you'll have to reverse those and put them in:
const dataOne = {
type: "Feature",
properties: {},
geometry: {
type: "LineString",
coordinates: [
[-122.41510269913951, 37.77909036739809],
[39.5423, -77.0564]
]
}
};
Also, the sample code has some cruft in it. Edit the variable dataOne not the other unused place.
Now you'll see a line from San Fransisco to some random spot in the middle of Antarctica that was really easy to miss.
Just in case the link goes bad, the full code is:
import React, { Component } from "react";
import { render } from "react-dom";
import ReactMapGL, { Source, Layer } from "react-map-gl";
class App extends Component {
constructor(props) {
super(props);
this.state = {
viewport: {
latitude: 38.63738602787579,
longitude: -121.23576311149986,
zoom: 6.8,
bearing: 0,
pitch: 0,
dragPan: true,
width: 600,
height: 600
}
};
}
render() {
const { viewport } = this.state;
const MAPBOX_TOKEN =
"pk.eyJ1Ijoic21peWFrYXdhIiwiYSI6ImNqcGM0d3U4bTB6dWwzcW04ZHRsbHl0ZWoifQ.X9cvdajtPbs9JDMG-CMDsA";
const dataOne = {
type: "Feature",
properties: {},
geometry: {
type: "LineString",
coordinates: [
[-122.41510269913951, 37.77909036739809],
[39.5423, -77.0564]
]
}
};
return (
<ReactMapGL
{...viewport}
mapboxApiAccessToken={MAPBOX_TOKEN}
onViewportChange={(newViewport) => {
this.setState({ viewport: newViewport });
}}
>
<Source id="polylineLayer" type="geojson" data={dataOne}>
<Layer
id="lineLayer"
type="line"
source="my-data"
layout={{
"line-join": "round",
"line-cap": "round"
}}
paint={{
"line-color": "rgba(3, 170, 238, 0.5)",
"line-width": 5
}}
/>
</Source>
</ReactMapGL>
);
}
}
render(<App />, document.getElementById("root"));

Cluster creation in openlayer 6.4.3 - Uncaught TypeError: this.source.loadFeatures is not a function

I am trying to create cluster using ol 6.4.3. My script is
var cluster_data = {
"type": "Feature",
'features': [
{
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': [686213.47091037, 1093486.3776117],
},
},
{
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': [687067.04391223, 1094462.7275206],
},
},
{
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': [687214.60645801, 1094362.868384],
},
}
],
};
var features = new Array(3);
var source = new ol.layer.Vector({
features: new ol.format.GeoJSON().readFeatures(cluster_data),
});
var clusterSource = new ol.source.Cluster({
distance: 40,
source: source,
});
var styleCache = {};
var clusters = new ol.layer.Vector({
source: clusterSource,
style: function (feature) {
var size = feature.get('features').length;
var style = styleCache[size];
if (!style) {
style = new Style({
image: new CircleStyle({
radius: 10,
stroke: new Stroke({
color: '#fff',
}),
fill: new Fill({
color: '#3399CC',
}),
}),
text: new Text({
text: size.toString(),
fill: new Fill({
color: '#fff',
}),
}),
});
styleCache[size] = style;
}
return style;
},
});
map.addLayer(clusters);
I have added other 3 Tile layers map.getLayers().extend([bm,road,landmark]); and trying to add cluster over this. But getting error Uncaught TypeError: this.source.loadFeatures is not a function while adding cluster.
The map I got after adding my layer is
The error is because
var source = new ol.layer.Vector({
should be
var source = new ol.source.Vector({
Also the first type in the data before the features should be
"type": "FeatureCollection",
And if you are using the OpenLayers full build
new Style new CircleStyle new Stroke new Fill and new Text
should be
new ol.style.Style new ol.style.Circle new ol.style.Stroke new ol.style.Fill and new ol.style.Text
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://openlayers.org/en/v6.4.3/css/ol.css" type="text/css">
<!-- The line below is only needed for old environments like Internet Explorer and Android 4.x -->
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=requestAnimationFrame,Element.prototype.classList,URL"></script>
<script src="https://openlayers.org/en/v6.4.3/build/ol.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.6.1/proj4.js"></script>
<style>
html, body, .map {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="map" class="map"></div>
<script>
proj4.defs("EPSG:32643","+proj=utm +zone=43 +datum=WGS84 +units=m +no_defs");
ol.proj.proj4.register(proj4);
var cluster_data = {
"type": "FeatureCollection",
'features': [
{
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': [686213.47091037, 1093486.3776117],
},
},
{
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': [687067.04391223, 1094462.7275206],
},
},
{
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': [687214.60645801, 1094362.868384],
},
}
],
};
var source = new ol.source.Vector({
features: new ol.format.GeoJSON().readFeatures(cluster_data),
});
var clusterSource = new ol.source.Cluster({
distance: 40,
source: source,
});
var styleCache = {};
var clusters = new ol.layer.Vector({
source: clusterSource,
style: function (feature) {
var size = feature.get('features').length;
var style = styleCache[size];
if (!style) {
style = new ol.style.Style({
image: new ol.style.Circle({
radius: 10,
stroke: new ol.style.Stroke({
color: '#fff',
}),
fill: new ol.style.Fill({
color: '#3399CC',
}),
}),
text: new ol.style.Text({
text: size.toString(),
fill: new ol.style.Fill({
color: '#fff',
}),
}),
});
styleCache[size] = style;
}
return style;
},
});
var map = new ol.Map({
target: 'map',
layers: [
new ol.layer.Tile({
source: new ol.source.OSM()
})
],
view: new ol.View({
projection: "EPSG:32643"
})
});
map.addLayer(clusters);
map.getView().fit(source.getExtent());
map.getView().setZoom(map.getView().getZoom() - 6);
</script>
</body>
</html>

In mapbox how do I move a Feature to top (z-index wise)?

I have a layer full of state border "Features". When a user clicks on a State, I want to move that state's Feature to the top of the stack (z-index wise).
export function drawStateBorders() {
$.getJSON('https://www.mapbox.com/mapbox-gl-js/assets/us_states.geojson').then((data) => {
this.stateGeoJSON = data;
this.map
.addSource('states', {
type: 'geojson',
data,
})
.addLayer({
id: 'state-borders',
type: 'line',
source: 'states',
paint: {
'line-color': [
'case', ['boolean', ['feature-state', 'selected'], false],
'#8d8b76',
'#bfe2ab',
],
'line-width': [
'case', ['boolean', ['feature-state', 'selected'], false],
6,
3,
],
},
});
});
}
When I select the state
export function stateSelected(state) {
const stateFeatures = this.map.queryRenderedFeatures({
layers: ['state-borders'],
filter: [
'==', 'STATE_NAME', state,
],
});
const features = this.stateGeoJSON.features;
const currentFeature = stateFeatures[0];
if (!currentFeature) {
return;
}
// same state
if (currentFeature.id === this.selectedStateId) return;
// move to front HERE ?????
// old selected state
if (this.selectedStateId) {
this.map.setFeatureState({
source: 'states',
id: this.selectedStateId,
}, {
selected: false,
});
}
this.selectedStateId = currentFeature.id;
this.map.setFeatureState({
source: 'states',
id: this.selectedStateId,
}, {
selected: true,
});
}
So far I've tried
features.splice(features.indexOf(currentFeature), 1);
features.push(currentFeature);
this.map.getSource('states').setData(this.stateGeoJSON);
This seems to do some really crazy stuff to the array (duplicating some states, removing others). No idea what's happening
Adding the state to another layer worked (thanks #AndrewHarvey for the advice).
In case anyone is interested here is my code
export function stateSelected(state) {
const features = this.stateGeoJSON.features;
const currentFeature = features.find(s => s.properties.NAME === state);
if (!currentFeature) {
return;
}
// removes active layer
this.removeLayersContaining('state-borders-active');
this.drawActiveStateLayer(currentFeature);
}
export function drawActiveStateLayer(feature) {
this.map
.addLayer({
id: 'state-borders-active',
type: 'line',
source: {
type: 'geojson',
data: feature
},
paint: {
'line-color': '#8d8b76',
'line-width': 6,
},
});
}
export function drawStateBorders() {
$.getJSON('states.json').then((data) => {
this.stateGeoJSON = data;
this.map
.addSource('states', {
type: 'geojson',
data,
})
.addLayer({
id: 'state-borders',
type: 'line',
source: 'states',
paint: {
'line-color': '#bfe2ab',
'line-width': 3,
},
});
});
Also this is the shapefile I'm using: http://eric.clst.org/assets/wiki/uploads/Stuff/gz_2010_us_040_00_500k.json