I have a geojson mapbox tileset visualized on a map. I am currently able to draw shapes such as polygons,polylines and add points to the map. But I now want to limit the draw tool to only allow drawing within the boundaries of the geojson layer. How would I do this? What I currently has, lets the cursor change to a pan symbol when it's outside of the map, and to a crosshair symbol when inside the map.
But once I click the draw tool buttons, it overrides this and lets the user draw anywhere they want.
mapboxgl.accessToken = config.MAPBOX_PUBLIC;
const map = new mapboxgl.Map({
attributionControl: false,
container: "map", // container ID
style: "mapbox://styles/mapbox/outdoors-v10",
center: [-69.45, 45.07], // starting position [lng, lat]
zoom: 8, // starting zoom
});
export function makeClickableArea() {
map.on("load", () => {
map.addSource('maine', {
'type': 'geojson',
'data': {
'type': 'Feature',
'geometry': {
'type': 'Polygon',
// These coordinates outline Maine.
'coordinates': [
[
[-67.13734, 45.13745],
[-66.96466, 44.8097],
[-68.03252, 44.3252],
[-69.06, 43.98],
[-70.11617, 43.68405],
[-70.64573, 43.09008],
[-70.75102, 43.08003],
[-70.79761, 43.21973],
[-70.98176, 43.36789],
[-70.94416, 43.46633],
[-71.08482, 45.30524],
[-70.66002, 45.46022],
[-70.30495, 45.91479],
[-70.00014, 46.69317],
[-69.23708, 47.44777],
[-68.90478, 47.18479],
[-68.2343, 47.35462],
[-67.79035, 47.06624],
[-67.79141, 45.70258],
[-67.13734, 45.13745]
]
]
}
}
});
// Add a new layer to visualize the polygon.
map.addLayer({
'id': 'maine',
'type': 'fill',
'source': 'maine', // reference the data source
'layout': {},
'paint': {
'fill-color': '#0080ff', // blue color fill
'fill-opacity': 0.5
}
});
// Add a black outline around the polygon.
map.addLayer({
'id': 'outline',
'type': 'line',
'source': 'maine',
'layout': {},
'paint': {
'line-color': '#000',
'line-width': 3
}
});
});
changeMouseSymbol();
}
// add the draw tool
function changeMouseSymbol() {
const modes = MapboxDraw.modes;
var draw = new MapboxDraw({
displayControlsDefault: false,
// Select which mapbox-gl-draw control buttons to add to the map.
controls: {
point: true,
line_string: true,
polygon: true,
},});
map.addControl(draw);
map.on("mouseover", "maine", () => {
map.getCanvas().style.cursor = "crosshair";
});
map.on("mouseleave", "maine", () => {
map.getCanvas().style.cursor = "";
});
}
Related
I have created the map with Mapbox, but I had a few problems. When I try to Place the icon in the center of the line and add the arrow to the line endpoint, it always fails.
I added Markers on Jorge Chavez International Airport(LIM) & Alejandro Velasco Astete International Airport (CUZ)
const marker1 = new mapboxgl.Marker({ color: '#5C7F74',scale:'0.8'})
.setLngLat([-77.11174632225463, -12.02384281134897])
.addTo(map);
const marker3 = new mapboxgl.Marker({ color: '#8BAAA1',scale:'0.5'})
.setLngLat([-70.15810500000016,-15.469970999998012])
.addTo(map);
then add a line to map, So far it's okay but I don't know how to build new layers, add arrows aligns to the line endpoint, or add a symbol on line center
map.on('load', () => {
map.addSource('route', {
'type': 'geojson',
'data': {
'type': 'Feature',
'properties': {},
'geometry': {
'type': 'LineString',
'coordinates': [
[-70.15810500000016,-15.469970999998012],[-77.10670100000081,-12.030560999998116]
]
}
}
});
map.addLayer({
'id': 'route',
'type': 'line',
'source': 'route',
'layout': {
'line-join': 'round',
'line-cap': 'round',
'line-miter-limit': 2
},
'paint': {
'line-color': '#888',
'line-width': 3,
'line-dasharray':[2,2]
}
});
});
I see in the documentation that icon-opacity supports feature-state,
https://docs.mapbox.com/mapbox-gl-js/style-spec/layers/#paint-symbol-icon-opacity
Therefore I use this code for making a change of opacity when user hovers over a icon:
map.addLayer({
type: 'symbol',
layout: {
'icon-image': 'point',
...
},
paint: {
'icon-opacity':
['case', ['boolean', ['feature-state','hover'], false], 1, 0.3]
}
});
The problem is that it takes the original opacity right(0.3) but does not change on hover.
Any idea?
Thanks.
The code you´re posting is right, but it's only half of the solution.
You already implemented the definition of the layer paint behavior for any feature (by default 0.3 as you say), the second part is to change the feature-state hover to true on mouseover.
The state of the features on 'hover' in your symbol layer don´t change automatically, you need to change it to true in a map.on('mousemove', 'youLayerId'... method, and again to false in a map.on('mouseout', 'youLayerId'... .
Check out this fiddle I have created for you on how to change opacity of an icon.
The relevant code is below
mapboxgl.accessToken = 'PUT HERE YOUR TOKEN';
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v11'
});
let fHover = null;
map.on('load', function() {
map.loadImage(
'https://upload.wikimedia.org/wikipedia/commons/7/7c/201408_cat.png',
function(error, image) {
if (error) throw error;
map.addImage('cat', image);
map.addSource('point', {
'type': 'geojson',
'generateId': true,
'data': {
'type': 'FeatureCollection',
'features': [{
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': [0, 0]
}
}]
}
});
map.addLayer({
'id': 'points',
'type': 'symbol',
'source': 'point',
'layout': {
'icon-image': 'cat',
'icon-size': 0.25,
},
'paint': {
'icon-opacity': [
'case',
['boolean', ['feature-state', 'hover'], false],
1,
0.3
]
}
});
}
);
map.on('mousemove', 'points', function(e) {
if (e.features[0]) {
mouseover(e.features[0]);
} else {
mouseout();
}
});
map.on('mouseout', 'points', function(e) {
mouseout();
});
function mouseover(feature) {
fHover = feature;
map.getCanvasContainer().style.cursor = 'pointer';
map.setFeatureState({
source: 'point',
id: fHover.id
}, {
hover: true
});
}
function mouseout() {
if (!fHover) return;
map.getCanvasContainer().style.cursor = 'default';
map.setFeatureState({
source: 'point',
id: fHover.id
}, {
hover: false
});
fHover = null;
}
});
Important, to change a feature state every feature must have an id in the source, so I strongly recommend to set always 'generateId': true in the addSource method.
PS.- If this answer solves your question, please mark it as answer accepted, in that way it will also help other users to know it was the right solution.
I have a custom line in mapbox that displays a popup when you click it, how can I make it so it shows only when you hover it ? I changed from "click" to "mouseenter" but it did not closed the popup. What needs changing ?
Here is a jsfiddle example : https://jsfiddle.net/andrre/jb4czmew/4/
JS
mapboxgl.accessToken = 'pk.eyJ1IjoibWFya2V0aW5nYnNvIiwiYSI6ImNrYnYwZmk3YjAxZjgyem1wY2Zmc3F4Y2EifQ.gMF-eCCaAHHgWIUoRcnfkg';
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v11',
center: [-0.066985, 51.503363],
zoom: 9
});
map.on('load', function() {
map.addSource('routeThree', {
'type': 'geojson',
'data': {
'type': 'Feature',
'properties': {},
'geometry': {
'type': 'LineString',
'coordinates': [
[-0.066985, 51.503363],
[-3.550610, 40.390555 ],
[-77.140217, 38.801481],
[-118.175979, 34.008447]
]
}
}
});
map.addLayer({
'id': 'routeThree',
'type': 'line',
'source': 'routeThree',
'layout': {
'line-join': 'round',
'line-cap': 'round'
},
'paint': {
'line-color': '#04A5BD',
'line-width': 8
}
});
map.on('click', 'routeThree', function(e) {
new mapboxgl.Popup()
.setLngLat(e.lngLat)
.setHTML("<h2>This is the third line that will explain something</h2>")
.addTo(map);
});
});
Your code is right, but there's a small mistake in it, you chose the same id for the Source and for the Layer... just call it differently in the map.addLayer and click event, and it works fiddle
mapboxgl.accessToken = 'pk.eyJ1IjoibWFya2V0aW5nYnNvIiwiYSI6ImNrYnYwZmk3YjAxZjgyem1wY2Zmc3F4Y2EifQ.gMF-eCCaAHHgWIUoRcnfkg';
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v11',
center: [-0.066985, 51.503363],
zoom: 9
});
map.on('load', function () {
var popup;
map.addSource('routeThree', {
'type': 'geojson',
'data': {
'type': 'Feature',
'properties': {},
'geometry': {
'type': 'LineString',
'coordinates': [
[-0.066985, 51.503363],
[-3.550610, 40.390555],
[-77.140217, 38.801481],
[-118.175979, 34.008447]
]
}
}
});
map.addLayer({
'id': 'routeThree2',
'type': 'line',
'source': 'routeThree',
'layout': {
'line-join': 'round',
'line-cap': 'round'
},
'paint': {
'line-color': '#04A5BD',
'line-width': 8
}
});
map.on('mouseover', 'routeThree2', function (e) {
popup = new mapboxgl.Popup()
.setLngLat(e.lngLat)
.setHTML("<h2>This is the third line that will explain something</h2>")
.addTo(map);
});
map.on('mouseout', 'routeThree2', function (e) {
if (popup) popup.remove();
});
});
I have built a website using Mapbox GL JS to display a number of layered routes and points on a map to track different teams progress on a route. However when testing on a large number of page reloads the tracks on the map along with a number of other page elements sometimes don't load and I get a Style is not done loading error.
Code Extract:
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/outdoors-v11',
center: [15, 50],
zoom: 4 // zoom range is 0 (the whole world) to 22 (street level)
});
var distanceContainer = document.getElementById('distance');
// GeoJSON object to hold our measurement features
var geojson = {
'type': 'FeatureCollection',
'features': []
};
var finishPoint = {
'type': 'FeatureCollection',
'features': []
};
var progressLine = {
'type': 'FeatureCollection',
'features': []
};
map.on('load', function () {
var routeLineString = getRoute();
var line = routeLineString;
var options = {units: 'kilometers'};
var endPoint = turf.along(line, turf.length(line), options);
map.addSource('route-source', {
'type': 'geojson',
'data': line
});
// Add styles to the map. Style layer: A style layer ties together the source and image and specifies how they are displayed on the map.
// 'line-color': '#0C7CBB', // dark cyan
// NHS blue: 005EB8
map.addLayer({
'id': 'route',
'type': 'line',
'source': 'route-source',
'layout': {
'line-join': 'round',
'line-cap': 'round'
},
'paint': {
'line-color': '#000000',
'line-width': 3
}
},"team1RouteProgress"); // placement of this line below the progress line layer
// destination marker
map.loadImage("https://i.imgur.com/MK4NUzI.png", function (error, image) {
if (error) throw error;
map.addImage("finish-flag", image);
map.addSource('finish-source', {
'type': 'geojson',
'data': finishPoint
});
map.addLayer({
id: 'finish',
type: 'symbol',
source: 'finish-source',
layout: {
"icon-image": "finish-flag",
'icon-anchor': "bottom"
}
});
});
finishPoint.features.push(endPoint);
//map.getSource('geojson').setData(geojson);
});
$(document).ready(function() {
// Clear the Distance container to populate it with a new value
distanceContainer.innerHTML = '';
var line = getRoute();
var options = {units: 'kilometers'};
$.ajax({ //create an ajax request to getProgress.php
type: "GET",
url: "./php/getTeamProgress.php",
dataType: "json", //expect json to be returned
data: {
//access_key: access_key,
},
success: function (response) {
var Teams_arr = [];
var dist_arr = [];
var colour_arr = [];
response.data.forEach(function (dat) {
Teams_arr.push(dat.team);
dist_arr.push(dat.distance);
//Colour Setting
if(dat.team === "Typhoon Squadron"){
colour_arr.push("purple");
}else if (dat.team === "Gloucester Penguins; for Ben"){
colour_arr.push("red");
}else if (dat.team === "Community"){
colour_arr.push("yellow");
}else if(dat.team === "HMS Grimsby"){
colour_arr.push("navy");
}else if(dat.team === "Thunderer Squadron"){
colour_arr.push("blue");
}else{
colour_arr.push("grey");
}
});
//alert(response.totaldist);
var distStart = 0;
//Team 1
var team1DistAlongRoute = dist_arr[0]
var team1Along = turf.along(line, team1DistAlongRoute, options);
if (team1DistAlongRoute<0.1) {
team1DistAlongRoute = 0.1; // prevent error in lineSliceAlong if dist = 0
}
var team1SliceLine = turf.lineSliceAlong(line, distStart, team1DistAlongRoute, {units: 'kilometers'});
map.addSource('team1progress-source', {
'type': 'geojson',
'data': team1SliceLine
});
map.addLayer({
'id': 'team1RouteProgress',
'type': 'line',
'source': 'team1progress-source',
'layout': {
'line-join': 'round',
'line-cap': 'round'
},
'paint': {
'line-color': colour_arr[0],
'line-width': 5
}
});
// Progress marker- Purple
// Image: An image is loaded and added to the map.
map.loadImage("./assets/black.png", function (error, image) {
if (error) throw error;
map.addImage("team1custom-marker", image);
map.addSource('geojson', {
'type': 'geojson',
'data': geojson
});
map.addLayer({
id: 'team1Progress',
type: 'symbol',
source: 'geojson',
layout: {
"icon-image": "team1custom-marker",
'icon-anchor': "bottom"
}
});
});
geojson.features.push(team1Along);
progressLine.features.push(team1SliceLine);
//Team 2
var team2DistAlongRoute = dist_arr[1]
var team2Along = turf.along(line, team2DistAlongRoute, options);
if (team2DistAlongRoute<0.1) {
team2DistAlongRoute = 0.1; // prevent error in lineSliceAlong if dist = 0
}
var team2SliceLine = turf.lineSliceAlong(line, distStart, team2DistAlongRoute, {units: 'kilometers'});
map.addSource('team2progress-source', {
'type': 'geojson',
'data': team2SliceLine
});
map.addLayer({
'id': 'team2RouteProgress',
'type': 'line',
'source': 'team2progress-source',
'layout': {
'line-join': 'round',
'line-cap': 'round'
},
'paint': {
'line-color': colour_arr[1],
'line-width': 5
}
});
geojson.features.push(team2Along);
progressLine.features.push(team2SliceLine);
etc etc
The quick fix:
map = new Mapboxgl.map({ ... })
map.on('load', () => {
...add all your `addSource()`, `addLayer()` etc here.
});
The 'Style is not done loading' error is thrown by the _checkLoaded method in Mapbox GL JS's style.js. This method is called each time a modification is made to the style -- for example when the Map#addSource and Map#addLayer methods are called. In addition to making use of the map#on('load', function() {}) listener to ensure that all necessary map resources are loaded before attempting to make a modification to the map's style, you could also take a look at some of these examples from our documentation, which demonstrate strategies for updating map sources and layers dynamically:
Update a feature in realtime.
Add live realtime data.
Using the GeoJSONSource#setData method to update an existing source's data and re-render the map.
Change a layer's color with buttons.
I am using a mapbox example in order to create multiple polygons on a map, and I have pop-up event for each. My problem is that I need to set each polygon's fill color differently based on it's geojson properties.
This is my example.
I am using the following javascript code:
mapboxgl.accessToken = 'pk.eyJ1IjoibWFoYW5tZWhydmFyeiIsImEiOiJ6SDdSWldRIn0.8zUNm01094D1aoSeHpWYqA';
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v9',
center: [51.40545845031738,
35.75069181054449],
zoom: 10
});
map.on('load', function (e) {
// Add a layer showing the state polygons.
map.addLayer({
'id': 'states-layer',
'type': 'fill',
'source': {
'type': 'geojson',
'data': 'geojson.js'
},
'paint': {
'fill-color': 'rgba(200, 100, 240, 0.4)',
'fill-outline-color': 'rgba(200, 100, 240, 1)'
}
});
// When a click event occurs on a feature in the states layer, open a popup at the
// location of the click, with description HTML from its properties.
map.on('click', 'states-layer', function (e) {
new mapboxgl.Popup()
.setLngLat(e.lngLat)
//.setHTML(e.features[0].properties.name)
.setHTML("<h1>"+e.features[0].properties.userone+"</h1>"+e.features[0].properties.name)
.addTo(map);
});
// Change the cursor to a pointer when the mouse is over the states layer.
map.on('mouseenter', 'states-layer', function () {
map.getCanvas().style.cursor = 'pointer';
});
// Change it back to a pointer when it leaves.
map.on('mouseleave', 'states-layer', function () {
map.getCanvas().style.cursor = '';
});
});
Here it loads the colors all the same
'paint': {
'fill-color': 'rgba(200, 100, 240, 0.4)',
'fill-outline-color': 'rgba(200, 100, 240, 1)'
}
On my geojson file I have a key for color:
"type": "Feature",
"properties": {
"userone":"پیروزی",
"name":"North Dafkota",
"featureclass":"Admin-1 scale rank",
"color":"red"
}
I want to use it to define the polygons fill color.
If you just want to use a color that you define in geojson feature properties. Then you could use the layers identity property like this:
map.addLayer({
'id': 'states-layer',
'type': 'fill',
'source': {
'type': 'geojson',
'data': 'geojson.js'
},
'paint': {
'fill-color': {
type: 'identity',
property: 'color',
},
'fill-outline-color': 'rgba(200, 100, 240, 1)'
}
});
Also see: https://www.mapbox.com/mapbox-gl-js/style-spec/#function-type
And: https://www.mapbox.com/mapbox-gl-js/style-spec/#types-color