How can I measure the distance between where I'm at the moment and the first circle I have drawn on the map? - mapbox

I have an application where I can draw a route so a simulation start where a boat is following that route. What I want to know is how I could measure the distance between me and the first Circle that I draw in the route(so the start point of the boat)
the circle on the right is my current location and the circle on the left is the first dot that I have drawn.
this is how I get my current location
navigator.geolocation.getCurrentPosition(position => {
console.log(position.coords.latitude, position.coords.longitude);
});
and this is how I draw and get the coordinates of the route
// gives the function to be able to draw lines in the map to make a route
var draw = new MapboxDraw({
displayControlsDefault: false,
controls: {
line_string: 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",
},
}
]
});
map.addControl(new mapboxgl.FullscreenControl());
map.addControl(geolocate)
map.addControl(new mapboxgl.NavigationControl());
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;
coords = data.features[lastFeature].geometry.coordinates;
}
So basically I want to measure between the distance between position.coords.latitude, position.coords.longitude and coords[0]

You can calculate the distance between two coordinates using LngLat.distanceTo()
Something like:
const start = new Mapbox.LngLat(...coords);
const end = new Mapbox.LngLat(position.coords.longitude, position.coords.latitude);
const dist = start.distanceTo(end);

Related

mapbox-gl-draw data driven style on LineString

I use Mapbox GL Draw and I want to customize the fill color of my LineString Feature using data driven.
I have a set userProperties: true and I have a property prefixed with user_ .
here is my style configuration :
{
id: "gl-draw-linestring-fill-inactive",
type: "fill",
filter: ["all", ["==", "active", "false"], ["==", "$type", "LineString"],["!=", "mode", "static"],],
paint: {
"fill-color": [
"case",
["==", ["get", "user_type"], "meetingRoom"],
"#00ff00",
["==", ["get", "user_type"], "notUsed"],
"#00ff00",
"#ff0000",
],
"fill-outline-color": "#3bb2d0",
"fill-opacity": 0.4,
},
}
and my feature :
{
"type": "Feature",
"id": "ROOM-floor-1-1",
"properties": {
"parentId": "floor-1",
"user_type": "notUsed"
},
"geometry": {
"coordinates": [
[2.334699793548168, 48.85506145052912],
[2.3334337908935083, 48.85340956808176],
[2.3360301692199243, 48.85314130852265],
[2.3368884761040363, 48.85547088304844],
[2.3368884761040363, 48.85547088304844],
[2.334699793548168, 48.85506145052912]
],
"type": "LineString"
}
}
Feature is always paint with default value (#ff0000). It should be #00ff00 in this example.
In the same application I use the same property (user_type) to set Line color on Polygon and it works fine !
Any Idea ?
I just figured out how to do it, I'm putting the answer here in case other people make the same mistake as me.
I misunderstood the mapbox documentation for using my own properties in data driven styles.
If you want to use a property named myProp from your feature, you have to prefix it with user_ but only in the style rule.
For example:
{
"type": "Feature",
"id": "1",
"properties": {
"myProp": "myValue"
},
"geometry": {
"coordinates": [...],
"type": "LineString"
}
}
And:
{
id: 'my-rule',
type: 'line',
filter: ['all', ['==', 'active', 'false'], ['==', '$type', 'LineString'],['!=', 'mode', 'static']],
paint: {
'line-color': [
'match', ['get', 'user_myProp'], // get the property
'myValue', '#00ff00',
'#ff0000']
},
}
My mistake was to add prefix user_ in the style rule AND the feature properties.
I dont really understand, why you are using "type: fill" in your style configuration for a linestring. You shoud be using the line-specific style properties as shown in this mapbox example https://docs.mapbox.com/mapbox-gl-js/example/data-driven-lines/
Also, since you are refering to a property in your data driven styling, there is no need to use the "case". you can simply use "match"
So it would rather be:
{
id: 'gl-draw-linestring-fill-inactive',
type: 'line',
filter: ['all', ['==', 'active', 'false'], ['==', '$type', 'LineString'],['!=', 'mode', 'static']],
paint: {
'line-color': [
'match', ['get', 'user_type'], // get the property
'meetingRoom', '#00ff00',
'notUsed', '#00ff00',
'#ff0000'],
'line-width': 3,
},
}
By the way: ids on feature level should be integers or strings, that can be cast in as integers:
https://github.com/mapbox/mapbox-gl-js/issues/7632

Mapbox looses popups when switching between satellite and street view

On Mapbox street map view we are drawing a geo json line and placing popups at each of the waypoints.
When switching from street map view the satellite view the waypoints disappear.
The attached fiddle contains both the style layers for street map and satellite so that you can switch between the two and see the information change for satellite.
What is the reason for this? How do we get the waypoints to appear on satellite view.
https://jsfiddle.net/Profiler/a0wemkjz/1/
mapboxgl.accessToken = 'pk.eyJ1IjoiY2xvbmc0MCIsImEiOiJjazNpeXV3a3MwY2Y4M2pxbGFybjZ5MTM4In0.HNQRjQR4y5V1koLlpenKUw';
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/clong40/ckbdypecg2ev71jqzc0xhdx54', //streetmap view, below is satelite view
// style: 'mapbox://styles/clong40/ckd74lwfm0l7f1is3iys2s6qx',
center: [3.1428,39.8669],
zoom: 7
});
// Add geolocate control to the map.
map.addControl(
new mapboxgl.GeolocateControl({
positionOptions: {
enableHighAccuracy: true
},
trackUserLocation: true
},
),'bottom-right'
);
/* Creates start point marker and listens to drag event */
var el = document.createElement('img');
el.src = '/assets/images/letter_s.png';
var startMarker = new mapboxgl.Marker(el,{draggable: true}).setLngLat([3.1428,39.8669]).addTo(map);
function onStartDragEnd() { var lngLat = this.getLngLat();$("#start").val(lngLat.lat.toFixed(4)+','+lngLat.lng.toFixed(4));}
startMarker.on('dragend', onStartDragEnd);
/* Creates end point marker and listens to drag event */
var el = document.createElement('img');
el.src = '/assets/images/letter_e.png';
var endMarker = new mapboxgl.Marker(el,{draggable: true}).setLngLat([4.0812,40.0724]).addTo(map);
function onEndDragEnd() { var lngLat = this.getLngLat();$("#end").val(lngLat.lat.toFixed(4)+','+lngLat.lng.toFixed(4));}
endMarker.on('dragend', onEndDragEnd);
/* Let's also listen to start and end text boxes to sync back with markers */
$(document).ready(function(){
$('#start').change(function(){if(!this.value) return; var parts = this.value.split(',');startMarker.setLngLat([parts[1].trim(),parts[0].trim()]);});
$('#end').change(function(){if(!this.value) return; var parts = this.value.split(',');endMarker.setLngLat([parts[1].trim(),parts[0].trim()]);});
});
map.on('load', function() {
map.addSource('route', {
'type': 'geojson',
'data': {
'type': 'Feature',
'properties': {},
'geometry': {
'type': 'LineString',
'coordinates': [
[3.1428091990252,39.866845647036],
[3.14265625,39.866640625],
[3.1425,39.8665625],
[3.141875,39.8671875],
[3.14375,39.869375],
[3.2,39.91],
[3.89,40.08],
[4.0812,40.0724],
]
}
}
});
map.addLayer({
'id': 'route',
'type': 'line',
'source': 'route',
'layout': {
'line-join': 'round',
'line-cap': 'round'
},
'paint': {
'line-color': '#333399',
'line-width': 4
}
});
});
//add markers
map.on('load', function() {
map.addSource('places', {
'type': 'geojson',
'data': {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"icon": "information",
"icon-allow-overlap": true,
"iconSize": "15"
},
"geometry": {
"type": "Point",
"coordinates": [3.1428091990252,39.866845647036]
}
}, {
"type": "Feature",
"properties": {
"icon": "information",
"icon-allow-overlap": true,
"iconSize": "15"
},
"geometry": {
"type": "Point",
"coordinates": [3.1425,39.8665625]
}
}, {
"type": "Feature",
"properties": {
"icon": "information",
"icon-allow-overlap": true,
"iconSize": "15"
},
"geometry": {
"type": "Point",
"coordinates": [3.14375,39.869375]
}
}, {
"type": "Feature",
"properties": {
"icon": "information",
"icon-allow-overlap": true,
"iconSize": "15"
},
"geometry": {
"type": "Point",
"coordinates": [3.89,40.08]
}
},
]
}
});
// Add a layer showing the places.
map.addLayer({
'id': 'places',
'type': 'symbol',
'source': 'places',
'layout': {
'icon-image': 'information',
'icon-size': 0.10,
'icon-allow-overlap': true
}
});
// When a click event occurs on a feature in the places layer, open a popup at the
// location of the feature, with description HTML from its properties.
map.on('click', 'places', function(e) {
var coordinates = e.features[0].geometry.coordinates.slice();
var description = e.features[0].properties.description;
// Ensure that if the map is zoomed out such that multiple
// copies of the feature are visible, the popup appears
// over the copy being pointed to.
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);
});
// Change the cursor to a pointer when the mouse is over the places layer.
map.on('mouseenter', 'places', function() {
map.getCanvas().style.cursor = 'pointer';
});
// Change it back to a pointer when it leaves.
map.on('mouseleave', 'places', function() {
map.getCanvas().style.cursor = '';
});
});
map.addControl(
new MapboxGeocoder({
accessToken: mapboxgl.accessToken,
mapboxgl: mapboxgl
}), 'top-left'
);
var map = new mapboxgl.Map({attributionControl: false})
.addControl(new mapboxgl.AttributionControl({
compact: true
}));
var canvas = map.getCanvasContainer();
map.addControl(new mapboxgl.NavigationControl(), 'bottom-right');
```
Looking at the console when I switch to the satellite style, I see this:
Image "information" could not be loaded. Please make sure you have
added the image with map.addImage() or a "sprite" property in your
style. You can provide missing images by listening for the
"styleimagemissing" map event.
This is referring to map.addSource('places', ...) where you set "icon": "information"
Using the styles API, you can check for the different sprites your styles have: https://docs.mapbox.com/api/maps/#retrieve-a-sprite-image-or-json
you can see in the sprites for your first style that you have a sprite called "information".
this is missing from your satellite style
in your JSFiddle, I swapped all references of "information" to "information-15" (a sprite that exists in both styles), and changed the icon-size to 1 in your addLayer and was able to switch between the styles and have the icons appear in both
map.addSource('places', {
'type': 'geojson',
'data': {
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"properties": {
"icon": "information-15",
"icon-allow-overlap": true,
"iconSize": "15"
},
"geometry": {
"type": "Point",
"coordinates": [3.1428091990252, 39.866845647036]
}
}, {
"type": "Feature",
"properties": {
"icon": "information-15",
"icon-allow-overlap": true,
"iconSize": "15"
},
"geometry": {
"type": "Point",
"coordinates": [3.1425, 39.8665625]
}
}, {
"type": "Feature",
"properties": {
"icon": "information-15",
"icon-allow-overlap": true,
"iconSize": "15"
},
"geometry": {
"type": "Point",
"coordinates": [3.14375, 39.869375]
}
}, {
"type": "Feature",
"properties": {
"icon": "information-15",
"icon-allow-overlap": true,
"iconSize": "15"
},
"geometry": {
"type": "Point",
"coordinates": [3.89, 40.08]
}
}, ]
}
});
// Add a layer showing the places.
map.addLayer({
'id': 'places',
'type': 'symbol',
'source': 'places',
'layout': {
'icon-image': 'information-15',
'icon-size': 1,
'icon-allow-overlap': true
}
});

How filter features by status

I want to show in my map in a cluster layer filtering by if is opened or not. How can do it? Should I create two layers?
One with filter: filter["has", "opened"] and other with filter: filter["!", ["has", "opened"]]?
export const clusterLayerOpened = {
id: "clusters",
type: "circle",
source: "earthquakes",
filter: ["has", "opened"],
paint: {
"circle-color": [ "step", ["get", "opened"], "#51bbd6",100,"#f1f075",750,"#f28cb1", ],
"circle-radius": ["step", ["get", "opened"], 20, 100, 30, 750, 40],
},
};
export const clusterLayerNoOpened = {
id: "clusters",
type: "circle",
source: "earthquakes",
filter: ["!", ["has", "opened"]],
paint: {
"circle-color": [ "step", ["get", "opened"], "#51bbd6",100,"#f1f075",750,"#f28cb1", ],
"circle-radius": ["step", ["get", "opened"], 20, 100, 30, 750, 40],
},
};
This is my geojson:
{
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"properties": {
"id": "ak16994521",
"mag": 2.3,
"time": 1507425650893,
"felt": null,
"tsunami": 0,
"opened": true
},
"geometry": {
"type": "Point",
"coordinates": [-151.5129, 63.1016, 0.0]
}
},
{
"type": "Feature",
"properties": {
"id": "ak16994519",
"mag": 1.7,
"time": 1507425289659,
"felt": null,
"tsunami": 0,
"opened": false
},
"geometry": {
"type": "Point",
"coordinates": [-150.4048, 63.1224, 105.5]
}
}
]
}
It's not necessary to create two separate layers to filter by whether the a point has been opened or not. Here is some code showing how to add a layer which displays all points with the property "opened": true, and hides all points with "opened": false:
map.addLayer({
'id': 'opened',
'type': 'circle',
'source': 'points',
'paint': {
'circle-radius': 10,
'circle-opacity': ["match", ["to-string", ["get", "opened"]], 'true', 1 , 'false', 0, 0]
}
});
To instead show all points with the property "opened": false, you can switch the 'circle-opacity' expression to read:
["match", ["to-string", ["get", "opened"]], 'true', 0 , 'false', 1, 0]
This code makes use of a few Mapbox expressions. I've linked the documentation to each relevant expression here: match, to-string, and get.
Here is a JSFiddle where two layers are added to the map: https://jsfiddle.net/hpkzrm4n/. The points with "opened": true are shown in red and the points with "opened": false are shown in blue. Note that you will need to add your own Mapbox access token where indicated in order to view the results. Here is a screenshot, as a preview:

Mapbox GL: Get Layer IDs

I have a map with a few dozen layers, each with a unique ID. I have checkboxes to turn the layers on and off, for which I need a single array of all the layer IDs. I can't figure out how to loop through all of the map layers to capture the layer IDs. I tried using map.getLayer() but this returns the layer as an object, not the layer ID as a string. I want to loop through all of the map layers and push the layer ID strings to a new array. How do I do this?
mapboxgl.accessToken = "myaccesstoken";
var map = new mapboxgl.Map({
container: "map",
style: "mapbox://styles/mymapboxstyle",
center: [-71.0664, 42.358],
minZoom: 14 //
});
map.on("style.load", function () {
map.addSource("contours", {
type: "vector",
url: "mapbox://mapbox.mapbox-terrain-v2"
});
map.addSource("hDistricts-2017", {
"type": "vector",
"url": "mapbox://mysource"
});
map.addLayer({
"id": "contours",
"type": "line",
"source": "contours",
"source-layer": "contour",
"layout": {
"visibility": "none",
"line-join": "round",
"line-cap": "round"
},
"paint": {
"line-color": "#877b59",
"line-width": 1
}
});
map.addLayer({
"id": "Back Bay Architectural District",
"source": "hDistricts-2017",
"source-layer": "Boston_Landmarks_Commission_B-7q48wq",
"type": "fill",
"layout": {
"visibility": "none"
},
"filter": ["==", "OBJECTID", 13],
"paint": {
"fill-color": "#192E39",
"fill-outline-color": "#000000",
"fill-opacity": 0.5
}
});
});
var layerIds = [];
function getIds() {
//here I need to iterate through map layers to get id strings.
//how do I do this???
layerIds.push( ); //then push those ids to new array.
console.log(layerIds); //["contours", "Back Bay Architectural District"]
}
If kielni answer isn't convenient because of unknown reason, use map.getStyle().layers to get an array of object layers then map it to get an array of string ids.
var layers = map.getStyle().layers;
var layerIds = layers.map(function (layer) {
return layer.id;
});
You have the layer ids when you add the layer; you can save them then:
function addLayer(map, options, layerIds) {
map.addLayer(options);
layerIds.push(options.id);
}
addLayer(map, {
"id": "Back Bay Architectural District",
"source": "hDistricts-2017",
"source-layer": "Boston_Landmarks_Commission_B-7q48wq",
"type": "fill",
"layout": {
"visibility": "none"
},
"filter": ["==", "OBJECTID", 13],
"paint": {
"fill-color": "#192E39",
"fill-outline-color": "#000000",
"fill-opacity": 0.5
}
},
layerIds);

Color only the edge of a circle mapbox gl js

I want to show the outline of a circle on an interactive map (no fill) however, the paint options in mapbox-gl-js seem limited to fill only.
https://www.mapbox.com/mapbox-gl-style-spec/#layers-circle
var styles = [{
"id": 'points',
"interactive": true,
"type": "circle",
"source": "geojson",
"paint": {
"circle-radius": 5,
"circle-color": "#000
},
"filter": ["in", "$type", "Point"]
}, {
"type": "line",
"source": "geojson",
"layout": {
"line-cap": "round",
"line-join": "round"
},
"paint": {
"line-color": "#000",
"line-width": 2.5
},
"filter": ["in", "$type", "LineString"]
}];
Am i missing something or is this just not possible?
This is now possible, with circle-opacity.
E.g.:
"paint": {
"circle-opacity": 0,
"circle-stroke-width": 1,
"circle-stroke-color": #000
}
Not currently possible. Current top workaround appears to be layering two circles of slightly different sizes.
https://github.com/mapbox/mapbox-gl-js/issues/1713
https://github.com/mapbox/mapbox-gl-style-spec/issues/379
I'm having trouble running custom color 'match' and having opacity controls running simultaneously.
I can get both working, but not at the same time. See code below.
var coorAddresses = [ [ -75.7040473, 45.418067,"Medium" ], [-75.7040473, 45.418067, "Medium"], [-79.32930440000001, 43.7730495, "Unknown"]]
$.getJSON(coodAddresses, function(data) {
for(var itemIndex in data) {
// push new feature to the collection
featureCollection.push({
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [data[itemIndex][0], data[itemIndex][1]]
},
"properties": {
"size_by": data[itemIndex][2],
"color_by": data[itemIndex][2]
},
});
}
});
map.on('load', function () {
map.addLayer({
"id": "points",
"type": "circle",
"source": {
"type": "geojson",
"data": {
"type": "FeatureCollection",
"features": featureCollection
}
},
"paint": {
"circle-color": [
'match',
['get', 'size_by'],
'Easy',
'#e4f400',
'Medium',
'#f48a00',
'Unknown',
'#6af400',
/* other */ '#00e4f4'
],
"circle-radius": [
'match',
['get', 'size_by'],
'Easy',
4,
'Medium',
7,
'Unknown',
2,
/* other */ 1000
],
// "circle-opacity": 0, // color does not show if i uncomment these lines
// "circle-stroke-width": 1, // do not get desired 'hollow' circle unless these lines run
}});
Trying to troubleshoot.