The two functions of relevance are below.
I want to add custom properties to the markers. To be specific, the userID to the properties object. It doesn't have to be there but what i've done below does not work.
Is there no way to pass custom properties?
initMap:function(){
L.mapbox.accessToken = 'myToken';
Shop.el.map = L.mapbox.map('map');
Shop.el.retailersLayer = L.mapbox.featureLayer().addTo(Shop.el.map);
Shop.el.retailMarkers = [];
// add tile layer
L.tileLayer('myLayerKey', {
maxZoom: 18
}).addTo(Shop.el.map);
// By default, center map over Colorado
Shop.el.map.setView([39.7392, -104.9847], 12);
},
addGeoJSONToMap:function(retailers){
// format geoJSON
jQuery.each(retailers, function(index, val) {
// Push each on of the retail locations into the retailMarkers array
Shop.el.retailMarkers.push(
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [parseFloat(val.lng), parseFloat(val.lat)]
},
properties: {
'userId':'99', <------------------------ This does not work.
'marker-color': '#193441',
'marker-symbol': 'star-stroked',
title: ' val.name,
description: 'Marker Description'
},
}
);
});
// Add the retail location as markers to the map
Shop.el.retailersLayer.setGeoJSON({
type: 'FeatureCollection',
features: Shop.el.retailMarkers
});
},
Never mind. This DOES work. The property is then found in:
marker.features.userID
Related
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/.
I use layers to clusterize my markers. I initialize like this:
this.map.addSource('markers', {
type: 'geojson',
data: this.getData(),
cluster: true,
clusterMaxZoom: 14 // Max zoom to cluster points on
});
// Add circles for clusters
this.map.addLayer({
id: 'clusters',
type: 'circle',
source: 'markers',
filter: ['has', 'point_count'],
//...
});
this.map.addLayer({
id: 'unclustered-point',
type: 'circle',
source: 'markers',
filter: ['!', ['has', 'point_count']],
//...
});
I also have a popup when a marker is clicked:
this.map.on('click', 'unclustered-point', (e) => {
// ...
new mapboxgl.Popup({ offset: 20 })
.setLngLat(coordinates)
.setDOMContent(popupContent)
.addTo(this.map);
});
Next to the map, I have a table with all my markers. When the user click on it, I want to fly to this one on the map and open its dedicated popup.
I have an identifier (which I set on the properties in my geojson).
I couldn't find any solution about this. Any ideas?
I can fly to the marker using this, but I am not very satisfied about this solution, and, mostly, I cannot open the popup.
const marker: markerProps = (this.map.getSource('marker') as any)._data.features
.map(feature => feature.properties)
.find(props => props.id === id);
this.map.flyTo({ center: [props.lon, props.lat], zoom: 14 });
There is an example in the Mapbox docs that does something very similar to what you are describing here: https://docs.mapbox.com/help/tutorials/building-a-store-locator/#define-interactivity-functions
Sorry of this is a simple question but I have a Mapbox map with 2 layers running on my website. Each layer can be toggled on or off using the example code on the MapBox support site.
However it uses the name of the layers directly for the text of the menu buttons. Is there a way to change this to a more aesthetically pleasing title?
Many thanks
Col
Code added below :
map.on('load', function() {
// add source and layer for 2020 Tour
map.addSource('pjuk-2020-tour', {
type: 'vector',
url: 'mapbox://styles/silentwitness/cka13nxh44g1v1iqd7xxr1nys'
});
map.addLayer({
'id': 'pjuk-2020-tour',
'type': 'circle',
'source': 'pjuk-2020-tour',
'layout': {
// make layer visible by default
'visibility': 'visible'
},
'source-layer': 'pjuk-2020-tour'
});
map.addSource('pjuk-previous-tours', {
type: 'vector',
url: 'mapbox://styles/silentwitness/cka13nxh44g1v1iqd7xxr1nys'
});
map.addLayer({
'id': 'pjuk-previous-tours',
'type': 'circle',
'source': 'pjuk-previous-tours',
'layout': {
// make layer visible by default
'visibility': 'visible'
},
'source-layer': 'pjuk-previous-tours'
});
});
// enumerate ids of the layers
var toggleableLayerIds = ['pjuk-2020-tour', 'pjuk-previous-tours'];
// set up the corresponding toggle button for each layer
for (var i = 0; i < toggleableLayerIds.length; i++) {
var id = toggleableLayerIds[i];
var link = document.createElement('a');
link.href = '#';
link.className = 'active';
link.textContent = id;
Using this example as a model, there are many ways which you could go about doing this. One is to create a variable storing a mapping from the desired title to the name of the layer, so that clicking on the relevant button will use the textContent as a key in this mapping to retrieve a value representing the name of the layer. For example:
var titleToLayerName = {
'PJUK Previous Tours': 'pjuk_previous_tours',
'PJUK 2020 Tour': 'pjuk_2020_tour'
}
...
link.onclick = function(e) {
var clickedLayer = titleToLayerName[this.textContent];
...
};
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>
I have three kml layers in a map: polygons, lines, points.
The map is modified from the mobile-wms-vienna example. I have changed the layers, and changed the "Labels" button to alter the opacity on the polygon layer.
To ensure that all features can be seen I need set z-indexing.
However I would also like to be able to display a pop-up on the polygon layer, which is set as the lowest. I do not want to see popups from lines or points. (Points can have labels, lines do not need labeling). I have read many posts about the problems with select on multiple layers, but could not find a solution to how to make anything selectable when z-indexing is set.
Is there a way to do this? Preferably draw the layers in the order they are added to the map.
Or a label layer that moves with the map and zoom changes? Unfortunately, kml polygon labels are fixed to a point and so might disappear when the map is moved or zoomed in.
The entire map code is given below, as I am not sure if there is something else in my map that is affecting this behaviour.
var map;
var linetyle = new OpenLayers.Style({'strokeWidth': 2, 'strokeColor':"red",});
function init() {
document.documentElement.lang = (navigator.userLanguage || navigator.language).split("-")[0];
var layerPanel = new OpenLayers.Control.Panel({
displayClass: "layerPanel",
autoActivate: true
});
var opButton = new OpenLayers.Control({
type: OpenLayers.Control.TYPE_TOGGLE,
displayClass: "opButton",
eventListeners: {
activate: function() {
if (polygon) {polygon.setOpacity(0.4);}
},
deactivate: function() {
if (polygon) {polygon.setOpacity(0.9);}
}
}
});
layerPanel.addControls([opButton]);
var zoomPanel = new OpenLayers.Control.ZoomPanel();
// Geolocate control for the Locate button - the locationupdated handler
// draws a cross at the location and a circle showing the accuracy radius.
var geolocate = new OpenLayers.Control.Geolocate({
type: OpenLayers.Control.TYPE_TOGGLE,
bind: false,
watch: true,
geolocationOptions: {
enableHighAccuracy: false,
maximumAge: 0,
timeout: 7000
},
eventListeners: {
activate: function() {
map.addLayer(vector);
},
deactivate: function() {
map.removeLayer(vector);
vector.removeAllFeatures();
},
locationupdated: function(e) {
vector.removeAllFeatures();
vector.addFeatures([
new OpenLayers.Feature.Vector(e.point, null, {
graphicName: 'cross',
strokeColor: '#f00',
strokeWidth: 2,
fillOpacity: 0,
pointRadius: 10
}),
new OpenLayers.Feature.Vector(
OpenLayers.Geometry.Polygon.createRegularPolygon(
new OpenLayers.Geometry.Point(e.point.x, e.point.y),
e.position.coords.accuracy / 2, 50, 0
), null, {
fillOpacity: 0.1,
fillColor: '#000',
strokeColor: '#f00',
strokeOpacity: 0.6
}
)
]);
map.zoomToExtent(vector.getDataExtent());
}
}
});
zoomPanel.addControls([geolocate]);
map = new OpenLayers.Map({
div: "map",
theme: null,
projection: new OpenLayers.Projection("EPSG:3857"),
displayProjection: new OpenLayers.Projection("EPSG:4326"),
layers: [new OpenLayers.Layer.Google( "Google Satellite", {type: google.maps.MapTypeId.SATELLITE, numZoomLevels: 22})],
center: new OpenLayers.LonLat(149.1, -35.3).transform('EPSG:4326', 'EPSG:3857'),
zoom: 10,
units: "m",
maxResolution: 38.21851413574219,
controls: [
new OpenLayers.Control.Navigation(),
new OpenLayers.Control.Attribution(),
zoomPanel,
layerPanel
],
});
layerPanel.activateControl(opButton);
// Vector layer for the location cross and circle
var vector = new OpenLayers.Layer.Vector("Vector Layer");
var point = new OpenLayers.Layer.Vector("points", {
// rendererOptions: {zIndexing: 'true'},
projection: map.displayProjection,
strategies: [new OpenLayers.Strategy.BBOX()],
protocol: new OpenLayers.Protocol.HTTP({
url: "kml/point.kml",
format: new OpenLayers.Format.KML({
extractStyles: true,
extractAttributes: true
}) }) });
var line = new OpenLayers.Layer.Vector("line", {
// rendererOptions: {zIndexing: 'true'},
projection: map.displayProjection,
strategies: [new OpenLayers.Strategy.BBOX({resFactor: 1})],
styleMap: linetyle,
protocol: new OpenLayers.Protocol.HTTP({
url: "kml/line.kml",
format: new OpenLayers.Format.KML({ extractStyles: false,
extractAttributes: true
}) }) });
var polygon = new OpenLayers.Layer.Vector("Geology", {
// rendererOptions: {zIndexing: 'true'},
projection: map.displayProjection,
strategies: [new OpenLayers.Strategy.BBOX({resFactor: 1})],
protocol: new OpenLayers.Protocol.HTTP({
url: "kml/polygon.kml",
format: new OpenLayers.Format.KML({extractStyles: true,extractAttributes: true})
}) });
map.addLayers([point, line, polygon]);
polygon.setOpacity(0.5);
// point.setZIndex(1400);
// line.setZIndex(1300);
// polygon.setZIndex(1200);
// Select Features/Popup
select = new OpenLayers.Control.SelectFeature (polygon, line, point);
polygon.events.on({
"featureselected": onFeatureSelect,
"featureunselected": onFeatureUnselect
}),
line.events.on({
"featureselected": onFeatureSelect,
"featureunselected": onFeatureUnselect
}),
point.events.on({
"featureselected": onFeatureSelect,
"featureunselected": onFeatureUnselect
}),
map.addControl(select);
select.activate();
function onPopupClose(evt) {
select.unselectAll();
}
function onFeatureSelect(event) {
var feature = event.feature;
// Since KML is user-generated, do naive protection against
// Javascript.
var content = "<h2>"+feature.attributes.name + "</h2>" + feature.attributes.description;
if (content.search("<script") != -1) {
content = "Content contained Javascript! Escaped content below.<br>" + content.replace(/</g, "<");
}
popup = new OpenLayers.Popup.FramedCloud("chicken",
feature.geometry.getBounds().getCenterLonLat(),
new OpenLayers.Size(100,100),
content,
null, false, onPopupClose);
feature.popup = popup;
map.addPopup(popup);
}
function onFeatureUnselect(event) {
var feature = event.feature;
if(feature.popup) {
map.removePopup(feature.popup);
feature.popup.destroy();
delete feature.popup;
}
};
};
The map can be seen at http://quartzspatial.net/act/map_v2.html
The closest answer that may solve my problem is here, but I could not understand how to use the solutions, and after many attempts at putting code in different places, I gave up.
I have just been looking at similar problem earlier. You probably figured this out yourself by now but I would like to share my jsFiddle in which I use event listener on the map object instead of a select control.
You cannot use an OpenLayers select control with layers index. The activation event will always put the layers on top and setting the z index on the layers will disable the select control. I also wasn't able to make the solution with disabling the moveontop in the activate event work (in jsFiddle).
Please have a look at the event listener solution:
var map = new OpenLayers.Map({
div: "map",
projection: new OpenLayers.Projection("EPSG:3857"),
displayProjection: new OpenLayers.Projection("EPSG:4326"),
layers: [
new OpenLayers.Layer.OSM()],
controls: [
new OpenLayers.Control.Navigation(),
new OpenLayers.Control.ArgParser() ],
eventListeners: {
featureover: function(e) { if (e.feature.layer != vectors2) {
e.feature.renderIntent = "temporary";
e.feature.layer.drawFeature(e.feature); }
},
featureout: function(e) { if (e.feature.layer != vectors2) {
e.feature.renderIntent = "default";
e.feature.layer.drawFeature(e.feature); }
},
featureclick: function(e) { if (e.feature.layer != vectors2) {
e.feature.renderIntent = "select";
e.feature.layer.drawFeature(e.feature); }
}
}
});