I have map where I can draw lines, but after drawing the lines I can drag the line and circles to where I want it and I dont want that. I needs to be blocked so it stays at that place. I tried doing something like draggeble:false or trying to change stuff in the style but I couldn't figure it out
this is how my map.js looks like:
mapboxgl.accessToken = 'myToken';
const map = new mapboxgl.Map({
container: 'map', // container ID
style: 'mapbox://styles/mapbox/streets-v9', // style URL
center: [-96, 37.8], // starting position
zoom: 3, // starting zoom
});
map.addControl(new mapboxgl.FullscreenControl());
const geolocate = new mapboxgl.GeolocateControl()
map.addControl(geolocate)
map.on('load', function()
{
geolocate.trigger();
});
var draw = new MapboxDraw({
displayControlsDefault: false,
controls: {
line_string: true,
trash: true
},
styles: [
// ACTIVE (being drawn)
// line stroke
{
"id": "gl-draw-line",
"type": "line",
"filter": ["all", ["==", "$type", "LineString"], ["!=", "mode", "static"]],
"layout": {
"line-cap": "round",
"line-join": "round"
},
"paint": {
"line-color": "#3b9ddd",
"line-dasharray": [0.2, 2],
"line-width": 4,
"line-opacity": 0.7
}
},
{
"id": "gl-draw-polygon-and-line-vertex-halo-active",
"type": "circle",
"filter": ["all", ["==", "meta", "vertex"], ["==", "$type", "Point"], ["!=", "mode", "static"]],
"paint": {
"circle-radius": 10,
"circle-color": "#FFF"
}
},
// vertex points
{
"id": "gl-draw-polygon-and-line-vertex-active",
"type": "circle",
"filter": ["all", ["==", "meta", "vertex"], ["==", "$type", "Point"], ["!=", "mode", "static"]],
"paint": {
"circle-radius": 6,
"circle-color": "#3b9ddd",
},
}
]
});
// add the draw tool to the map
map.addControl(draw);
// add create, update, or delete actions
map.on('draw.create', updateRoute);
map.on('draw.update', updateRoute);
map.on('draw.delete', removeRoute);
// use the coordinates you just drew to make your directions request
function updateRoute() {
removeRoute(); // overwrite any existing layers
var data = draw.getAll();
var lastFeature = data.features.length - 1;
var coords = data.features[lastFeature].geometry.coordinates;
var newCoords = coords.join(';')
console.log(newCoords);
for (var i = 0; i < coords.length; i++) {
var coordsPoints = coords[i];
// Create a default Marker and add it to the map.
const marker1 = new mapboxgl.Marker()
.setLngLat([coordsPoints[0],coordsPoints[1]])
.addTo(map);
}
//console.log(newCoords);
}
// remove the layer if it exists
function removeRoute () {
if (map.getSource('route')) {
map.removeLayer('route');
map.removeSource('route');
document.getElementById('calculated-line').innerHTML = '';
} else {
return;
}
}
Image of my drawen lines
Related
I have the following code but when I click on a point to open a popup it returns 'undefined' and I cannot seem to work out why.
I'm pulling the description field from the geoJSON external source which I have control over but for some reason it just does not want to populate the description HTML in my array. I took the example for the popup from the mapbox website so I know it works there. I have checked, rechecked and triple checked but I think I cannot see the tree for the forest lol.
Could someone maybe help me please? thanks.
<script>
// ajax loading gif
$(document).ready(function () {
setTimeout(function () {
$("#ajax-loader").removeClass("is-active");
}, 6000);
});
// vars
var initialOpacity = 0.2;
var opacity = initialOpacity;
// mapbox api
mapboxgl.accessToken = "hidden_api_key";
var map = new mapboxgl.Map({
container: "map", // container ID
style: "mapbox://styles/mapbox/dark-v10",
center: [-2.582861, 53.5154517],
zoom: 5,
maxZoom: 16,
minZoom: 0,
});
map.on("load", function () {
// get hotspot locations
map.addSource("hotspot_locations", {
type: "geojson",
data: "https://netmaker.io/dashboard/public_actions.php?a=ajax_helium_miners_location",
cluster: false,
clusterMaxZoom: 10, // max zoom to cluster points on
clusterRadius: 50, // radius of each cluster when clustering points (defaults to 50)
});
// add 300m circle around each hotspot
map.addLayer({
id: "circle500",
type: "circle",
source: "hotspot_locations",
paint: {
"circle-radius": {
stops: [
[0, 1],
[16, 600],
],
base: 2,
},
"circle-color": "green",
"circle-opacity": 0.1,
"circle-stroke-width": 0,
"circle-stroke-color": "white",
},
});
// add hotspot location
map.addLayer({
id: "hotspots-layer",
type: "circle",
source: "hotspot_locations",
paint: {
"circle-radius": 2,
"circle-stroke-width": 2,
// "circle-color": "#36d293",
"circle-color": "white",
"circle-stroke-color": [
"match",
["get", "status"],
"online",
"#36d293",
"offline",
"#d23636",
"orange", // other
],
// "circle-stroke-color": '#36d293',
},
});
});
// ajax call hotspots location by name
var customData;
$.ajax({
async: false,
type: "GET",
global: false,
dataType: "json",
url: "https://netmaker.io/dashboard/public_actions.php?a=ajax_helium_miners_location_customdata",
success: function (data) {
customData = data;
},
});
// custom data using hotspot name
function forwardGeocoder(query) {
var matchingFeatures = [];
for (var i = 0; i < customData.features.length; i++) {
var feature = customData.features[i];
if (feature.properties.title.toLowerCase().search(query.toLowerCase()) !== -1) {
// feature["place_name"] = '<img src="https://netmaker.io/dashboard/images/helium_logo.svg" alt="" width="15px"> ' + feature.properties.title;
feature["place_name"] = feature.properties.title;
feature["center"] = feature.geometry.coordinates;
feature["place_type"] = ["hotspot"];
matchingFeatures.push(feature);
}
}
return matchingFeatures;
}
// add the control to the map.
map.addControl(
new MapboxGeocoder({
accessToken: mapboxgl.accessToken,
localGeocoder: forwardGeocoder,
zoom: 14,
placeholder: "Search: address or hotspot name",
mapboxgl: mapboxgl,
})
);
map.on("click", "hotspots-layer", function (e) {
var coordinates = e.features[0].geometry.coordinates.slice();
var description = e.features[0].properties.description;
while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;
}
new mapboxgl.Popup().setLngLat(coordinates).setHTML(description).addTo(map);
});
map.on("mouseenter", "hotspots-layer", function () {
map.getCanvas().style.cursor = "pointer";
});
map.on("mouseleave", "hotspots-layer", function () {
map.getCanvas().style.cursor = "";
});
</script>
You're recieving an undefined because e.features[0].properties.description doesn't exist in the "hotspots-layer" data.
"features": [
{
"type": "Feature",
"properties": {
"status": "online"
},
"geometry": {
"type": "Point",
"coordinates": [
"119.73479929772",
"30.240836896893",
0
]
}
},
The only "description" in this case that you can return is the e.features[0].properties.status as seen here:
map.on("click", "hotspots-layer", function (e) {
var coordinates = e.features[0].geometry.coordinates.slice();
var description = e.features[0].properties.status;
while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;
}
console.log(description)
new mapboxgl.Popup().setLngLat(coordinates).setHTML(description).addTo(map);
});
This code snippet will allow the click event to show you the status when you interact with the hotspot location.
I tried to render the polygon-shaped surfaces on the map whenever user clicks on polygon shape layer A popup
with polygon details is displayed and the layer can be edited.In the popup, there is option to delete the polygon. After Clicking on Delete on popup I tried reinitialize the map with new surfaces i.e(polygons) data but still, the selected surface is appearing.
componentDidUpdate(prevProps, prevState) {
const { user, surfaces } = this.props;
const { allLayers } = this.state;
const that = this;
let selectedSurface = null;
if (!prevProps.user.id && user.id) {
this.initializeMap();
}
if (this.props.deleteAction.success !== prevProps.deleteAction.success) {
this.props.actionFetch();
map.remove();
this.initializeMap();
}
if ((allLayers.length === 1 && surfaces.length) || (surfaces.length !==
prevProps.surfaces.length)) {
let allLayers = [{ key: -1, name: this.props.intl.formatMessage({ id:
'surface.allsurfaces' }), color: '#CCCCCC' }];
surfaces.forEach((o) => {
let l = L.geoJSON(o.geometry)._layers;
[l] = Object.keys(l).map(ob => l[ob]);
const customlayer = this.addPopupToLayer(o, l);
map.addLayer(drawnItems[o.surface_type.id].addLayer(customlayer));
l.on('click', (e) => {
if (selectedSurface) {
selectedSurface.editing.disable();
}
selectedSurface = e.target;
e.target.editing.enable();
that.setState({
popup: true,
detail: true,
surfaceDetail: o,
typeSelected: o.surface_type,
editSurface: selectedSurface
});
});
allLayers.push({
key: o.surface_type.id,
name: o.surface_type.name,
color: o.surface_type.color
});
});
allLayers = allLayers.filter(
(l, index, self) => self.findIndex(
t => t.key === l.key
) === index
);
this.setState({
allLayers,
counter: surfaces.length
});
}
}
initializeMap() {
const { user, actionFetch, actionFetchTypes } = this.props;
actionFetch();
actionFetchTypes();
map = L.map('map', {
center: [...user.airport.location.coordinates].reverse(),
zoom: 15,
editable: true,
});
L.gridLayer.googleMutant({ type: 'satellite', maxZoom: 20 }).addTo(map);
const that = this;
map.on(L.Draw.Event.CREATED, (e) => {
drawnItems[that.state.typeSelected.key].addLayer(e.layer);
utils.toggleZooming(map, 'disable');
that.setState({ popup: true, layer: e.layer });
});
map.on('draw:deleted', (e) => {
that.setState({ popup: false });
});
}
.In the popup, there is option to delete the polygon.
Please check below example.
// initialize the map
var map = L.map('map', {
center: [0.4, 102],
zoom: 7
});
// add map layer (OpenStreetMap)
L.tileLayer('https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '© OpenStreetMap, Tiles courtesy of Humanitarian OpenStreetMap Team'
}).addTo(map);
// load example GEOJSON (from Wikipedia)
var geojsonFeature = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [102.0, 0.5]
},
"properties": {
"prop0": "A"
}
},{
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": [
[102.0, 0.0], [103.0, 1.0], [104.0, 0.0], [105.0, 1.0]
]
},
"properties": {
"prop0": "B",
"prop1": 0.0
}
},{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ]
]
},
"properties": {
"prop0": "C",
"prop1": {"this": "that"}
}
}
]
};
// load GEOJSON object/array to map
L.geoJSON(geojsonFeature, {
// style features based on properties
style: function(feature) {
switch(feature.properties.prop0){
case 'B': return { color: "red" }
case 'C': return { color: "green" }
}
},
// replace default maker with circle for point feature
pointToLayer: function(feature, latlng) {
return L.circleMarker(latlng, {
radius: 14,
fillColor: "orange",
color: "orange",
weight: 2,
opacity: 1,
fillOpacity: 0.5
});
},
// bind tooltip to each feature
onEachFeature: function(feature, layer) {
var popupContent = "<button onclick='removeSelectedLayer(\""+feature.properties.prop0+"\")'>Click here to remove this polygon</button><p>";
if (feature.properties.prop0) {
popupContent += feature.properties.prop0;
}
layer.bindPopup(popupContent);
layer.myTag = feature.properties.prop0
}
}).addTo(map);
function removeSelectedLayer(layerName) {
map.eachLayer( function(layer) {
console.log(layer.myTag)
if ( layer.myTag && layer.myTag === layerName) {
map.removeLayer(layer)
}
});
}
#map {
height: 500px;
}
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.6.0/dist/leaflet.css" crossorigin=""/>
<script src="https://unpkg.com/leaflet#1.6.0/dist/leaflet.js"crossorigin=""></script>
<div id="map"></div>
Hope this will helps you
I try to import styling with the stylingfunction from ol-mapbox-style. But it doesn't work.
- Without the stylesection the map renders perfectly fine as just the vector lines
- With the stylesection included the browser doesn't show the map
Please see the code below:
var stylefunction = olms.stylefunction;
var {applyStyle} = olms;
var {apply} = olms;
var key = 'my-key';
const map = new Map({
target: 'map',
view: new View({
center: fromLonLat([5.058510183635008, 52.30659202560312]),
zoom: 14
})
});
const layer = new VectorTileLayer({
source: new VectorTileSource({
attributions: '',
format: new GeoJSON(),
maxZoom: 19,
url: 'https://tile.nextzen.org/tilezen/vector/v1/all/{z}/{x}/{y}.json?api_key=' + key,
tileLoadFunction: function(tile, url) {
tile.setLoader(function(extent, resolution, projection) {
fetch(url).then(function(response) {
response.text().then(function(data) {
const jsons = JSON.parse(data);
const format = tile.getFormat();
const layers = ['water', 'roads', 'buildings'];
const features = [];
layers.forEach(function(layer) {
const layerFeatures = format.readFeatures(jsons[layer], {
featureProjection: projection
});
layerFeatures.forEach(function(feature) {
feature.set('layer', layer);
features.push(feature);
});
});
tile.setFeatures(features);
});
});
});
}
})
});
map.addLayer(layer);
fetch('./static/bright.json')
.then(r => r.json())
.then((glStyle) => {
stylefunction(layer, glStyle, 'bright');
if (map.getLayers().getArray().indexOf(layer) === -1) {
map.addLayer(layer);
}
});
And see here below the first lines of bright.json which i'm trying to use as a testfile.
{
"glyphs": "https://maps.tilehosting.com/fonts/{fontstack}/{range}.pbf?key=",
"name": "bright",
"layers": [
{
"id": "background",
"paint": {
"background-color": "#f8f4f0"
},
"type": "background"
},
{
"filter": [
"==",
"subclass",
"glacier"
],
"id": "landcover-glacier",
"layout": {
"visibility": "visible"
},
"metadata": {
"mapbox:group": "1444849388993.3071"
},
"paint": {
"fill-color": "#fff",
"fill-opacity": {
"base": 1,
"stops": [
[
0,
0.9
],
[
10,
0.3
]
]
}
},
"source": "openmaptiles",
Situation:
I use Mapbox to create a map with three markers + a linestring connecting them. Following this exmaple I created a button that zooms the camera to the bounds of the linestring.
This works as intended.
Whenever the function is called (on click and on first map-load), the camera zooms to the linestring bounds correctly.
Problem/Goal:
I would like to only display the button whenever:
the user has changed position of the campera, after linestring was brought into view
the user has changed the zoom, after linestring was brought into view
This can be simply done by adding / removing a .is-visible class.
However I somehow cant figure out how to listen to these two possible user interactions after linestring was brought into view.
I've tried some approaches that seemed overly complex and did not work. I have the feeling that the answer is quite simple, only I'm not seeing it.
Any help appreciated!
Code:
<script src='https://unpkg.com/mapbox#1.0.0-beta9/dist/mapbox-sdk.min.js'></script>
<script src='https://api.mapbox.com/mapbox-gl-js/v1.2.0/mapbox-gl.js'></script>
<link href='https://api.mapbox.com/mapbox-gl-js/v1.2.0/mapbox-gl.css' rel='stylesheet' />
<div id='map' class="traveljournal__map"></div>
<div class='traveljournal__map-actions'>
<div id='zoomto' class="traveljournal__map-action traveljournal__map-action--zoomto"></div>
</div>
<script>
mapboxgl.accessToken = 'TOKENHERE';
let client = new MapboxClient(mapboxgl.accessToken);
// DEFINE MAP
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/nilsdannemann/cjz2kdev503jo1dnsr23qoca8',
center: [174.724779, -41.288030], // Coordinates of newest Moment
zoom: 9
});
// DEFINE MOMENTS
var moments = [{
id: "1",
properties: {
title: "moment title",
content: "moment content",
mood: "happy",
date: "01. Aug 2019",
weather: "sunny",
iconSize: [60, 60],
location: [174.800314, -41.317955],
},
camera: {
center: [174.800314, -41.317955],
zoom: 13,
bearing: 20, // Add 20 for every location
pitch: 40
}
}, {
id: "2",
properties: {
title: "moment title",
content: "moment content",
mood: "happy",
date: "01. Aug 2019",
weather: "sunny",
iconSize: [60, 60],
location: [174.773008, -41.282235],
},
camera: {
center: [174.773008, -41.282235],
zoom: 13,
bearing: 40, // Add 20 for every location
pitch: 40
}
}, {
id: "3",
properties: {
title: "moment title",
content: "moment content",
mood: "happy",
date: "01. Aug 2019",
weather: "sunny",
iconSize: [60, 60],
location: [174.724779, -41.288030],
},
camera: {
center: [174.724779, -41.288030],
zoom: 13,
bearing: 60, // Add 20 for every location
pitch: 40
}
}];
// ADD MARKERS
moments.forEach(function(marker, index) {
// Create a DOM element for Marker
var el = document.createElement('div');
el.className = 'traveljournal__map-marker';
el.style.width = marker.properties.iconSize[0] + 'px';
el.style.height = marker.properties.iconSize[1] + 'px';
el.addEventListener('click', function() {
//Move Campera to Marker
map.flyTo(moments[index].camera);
});
// Add Marker to Map
new mapboxgl.Marker(el)
.setLngLat(marker.properties.location)
.addTo(map);
});
// ADD LINE BETWEEN MARKERS
var linestring = [];
moments.forEach(function(item) {
linestring.push(item.properties.location);
});
// ADD ZOOM TO LINESTRING FUNCTION
function zoomToLineString() {
var bounds = linestring.reduce(function(bounds, coord) {
return bounds.extend(coord);
}, new mapboxgl.LngLatBounds(linestring[0], linestring[0]));
map.fitBounds(bounds, {
padding: {top: 30, right: 0, bottom: 75, left: 0},
bearing: 0,
pitch: 0
});
};
// ADD ZOOM TO LINESTRING BUTTON EVENT
document.getElementById('zoomto').addEventListener('click', function() {
zoomToLineString();
});
// LOAD MAP
map.on('load', function() {
// ADD LINE TO MAP
map.addLayer({
"id": "route",
"type": "line",
"source": {
"type": "geojson",
"data": {
"type": "Feature",
"properties": {},
"geometry": {
"type": "LineString",
"coordinates": linestring
}
}
},
"layout": {
"line-join": "round",
"line-cap": "round"
},
"paint": {
"line-color": "#2BABEE",
"line-width": 2
}
});
// ZOOM TO LINESTRING ON MAP LOAD
zoomToLineString();
});
</script>
You can listen to the zoomend and moveend events to perform your bounds check, and update classes as required. You can use queryRenderedFeatures to see if the line is (partially) somewhere in the viewport.
function checkLine() {
if (map.queryRenderedFeatures({layers: 'route'}).length) {
// route is within view, do what you want
}
}
map.on('zoomend', checkLine);
map.on('moveend', checkLine);
i have to change the color or borders of buildings on click.
HTML & JS
Like the examples to hover countries, but with click and not countries -> buildings.
If it's easier with another plugin, please say so. 3D is not a 'must have'.
My Code:
<script>
mapboxgl.accessToken = 'hidden';
var map = new mapboxgl.Map({
style: 'mapbox://styles/mapbox/light-v9',
center: [7.3337859, 50.8403206],
zoom: 19.8,
pitch: 60,
bearing: -70,
hash: true,
container: 'map'
});
// The 'building' layer in the mapbox-streets vector source contains building-height
// data from OpenStreetMap.
map.on('load', function() {
// Insert the layer beneath any symbol layer.
var layers = map.getStyle().layers;
var labelLayerId;
for (var i = 0; i < layers.length; i++) {
if (layers[i].type === 'symbol' && layers[i].layout['text-field']) {
labelLayerId = layers[i].id;
break;
}
}
map.addLayer({
'id': '3d-buildings',
'source': 'composite',
'source-layer': 'building',
'filter': ['==', 'extrude', 'true'],
'type': 'fill-extrusion',
'minzoom': 15,
'paint': {
'fill-extrusion-color': '#aaa',
// use an 'interpolate' expression to add a smooth transition effect to the
// buildings as the user zooms in
'fill-extrusion-height': [
"interpolate", ["linear"], ["zoom"],
15, 0,
15.05, ["get", "height"]
],
'fill-extrusion-base': [
"interpolate", ["linear"], ["zoom"],
15, 0,
15.05, ["get", "min_height"]
],
'fill-extrusion-opacity': .6
}
}, labelLayerId);
map.on('click', '3d-buildings', function(e) {
console.log(e.features[0]);
//map.setPaintProperty('3d-buildings', 'fill-extrude-color', '#FF0000');
map.setPaintProperty('3d-buildings', 'fill-color', '#faafee');
});
});
Thanks :)
You need to add a layer on which to display the selected buildings. For example:
map.addSource('currentBuildings', {
type: 'geojson',
data: {
"type": "FeatureCollection",
"features": []
}
});
map.addLayer({
"id": "highlight",
"source": "currentBuildings",
'type': 'line',
'minzoom': 15,
'paint': {
'line-color': '#f00',
'line-width': 3
}
}, labelLayerId);
map.on('click', '3d-buildings', function(e) {
map.getSource('currentBuildings').setData({
"type": "FeatureCollection",
"features": e.features[0]]
});
});
[ https://jsfiddle.net/o50vy8jc/ ]