In leaflet 1.0 beta2, when clicking on a GeoJSON with a click function assigned along with a map click function defined, they both fire instead of just one. This doesn't happen in the older versions of leaflet. See fiddle for examples. Any workarounds for this?
Leaflet 7.7
http://jsfiddle.net/tator/5e209s9c/14/
Leaflet 1.0 beta2
http://jsfiddle.net/tator/em9cLfk4/4/
// Create the map
var map = L.map('map').setView([41, -98], 5);
//when map is clicked run identify
map.on('click', identify);
// Identify function
function identify(e) {
alert('click on the map');
};
//example geojson
var states = [{
"type": "Feature",
"properties": {"party": "Republican"},
"geometry": {
"type": "Polygon",
"coordinates": [[
[-104.05, 48.99],
[-97.22, 48.98],
[-96.58, 45.94],
[-104.03, 45.94],
[-104.05, 48.99]
]]
}
}, {
"type": "Feature",
"properties": {"party": "Democrat"},
"geometry": {
"type": "Polygon",
"coordinates": [[
[-109.05, 41.00],
[-102.06, 40.99],
[-102.03, 36.99],
[-109.04, 36.99],
[-109.05, 41.00]
]]
}
}];
//style the polygon with clickedgeojson function
parpoly = L.geoJson(states, {
style: {
color: '#ff7800',
weight: 1.5,
opacity: 1,
fillOpacity: 0
},
onEachFeature: function(feature, layer) {
layer.on({
click: clickedgeojson
});
}
});
//clickedgeojson function
function clickedgeojson(e) {
alert('click on json');
};
// Set up the OSM layer
L.tileLayer(
'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
{maxZoom: 18}).addTo(map);
//add the geojson to map
parpoly.addTo(map);
Use L.DomEvent's stopPropagation method:
Stop the given event from propagation to parent elements.
http://leafletjs.com/reference-1.0.0.html#domevent-stoppropagation
//clickedgeojson function
function clickedgeojson(e) {
L.DomEvent.stopPropagation(e);
alert('click on json');
};
Here's a working fork of your Fiddle: http://jsfiddle.net/hakw66nj/
Or you can add the click event to the nonBubblingEvents array in your layer's options object. This goes currently undocumented so i can't link to any documentation just to the commit on Github:
Add nonBubblingEvents option (fix #3604)
https://github.com/Leaflet/Leaflet/commit/74018f284e8c58d022a9a127406867438aa2a4d0
new L.GeoJSON(collection, {
nonBubblingEvents: ['click']
})
Here's a fork of your Fiddle using this solution: http://jsfiddle.net/hdd8rgkm/
iH8 gave you a good answer if you want to stop propagation altogether. However if you still want the map function to fire you can just add in a variable check. Example here.
var idGeo = 0;
// Identify function
function identify(e) {
if(idGeo ==1){
idGeo = 0;
}else{
alert('click on the map');
idGeo = 0;
}
};
//clickedgeojson function
function clickedgeojson(e) {
idGeo = 1;
alert('click on json');
};
Related
I've been trying to get Leaflet to display a geojson file using the styles described in the geojson file, and I can't get it to work. The geojson below shows that I've got styles in there - OGR style pen etc, but I've tried extracting them using style function(styles) {return {colour : data.properties.pen}}, but it gives me an error on the console - but not enough errors to match the number of layers - so I can understand that some layers may not have a "pen" property, but none of the layers are coming up with the any differences.
"features": [
{ "type": "Feature", "properties": { "Layer": "Buildings", "SubClasses": "AcDbEntity:AcDb2dPolyline", "EntityHandle": "2ABF", "OGR_STYLE": "PEN(c:#ff7f00,p:"1.2g 0.72g 0.12g 0.72g")" }, "geometry": { "type": "LineString", "coordinates": [ [ -1.386274792183286, 54.907452998026585, 0.0 ], [ -1.386201193400163,
In fact, as the above geojson shows, it's actually a geometry - but all that's showing up is a marker, which is my second problem. Can anyone point me to some example codes or anything which may help me?
$.getJSON(address, function(data) {
//add GeoJSON layer to the map once the file is loaded
layer[i] = L.geoJson(data, {style: function(styles) {
return {color: data.properties.pen,
weight: data.properites.weight
};
onEachFeature: onEachFeature
}
}).addTo(map);
Thanks.
Change your code to:
function onEachFeature(feature, layer) {
if (feature.properties && layer instanceof L.Path) {
layer.setStyle({
color: feature.properties.pen,
weight: feature.properites.weight
});
}
}
$.getJSON(address, function(data) {
//add GeoJSON layer to the map once the file is loaded
layer[i] = L.geoJson(data, {
onEachFeature: onEachFeature
}).addTo(map);
});
Leaflet GeoJson Tutorial
I'm using the mapbox supercluster library to cluster a fairly large amount of point data and plotting it with leaflet inside a shiny app. At maxZoom the points are shown and are getting draggable for the user in case the location is wrong. Mapbox supercluster uses a static kdbush spatial index, so each time a L.marker is moved, the cluster will be reloaded (not a problem in my case, because supercluster is extremely fast).
However, it is only possible to drag an L.marker used with supercluster inside the visible map bounds. This makes sense, because the L.marker's are generated on the fly for only the visible map bounds. If the L.marker are defined with options {draggable:true,autoPan:true} an error occurs in case L.marker is dragged outside of the visible map extent:
Uncaught TypeError: t is null
leaflet 1.3.3/dist/leaflet.js:5
leaflet 1.3.3/dist/leaflet.js:5
at https://unpkg.com/leaflet#1.3.3/dist/leaflet.js:5
_adjustPan https://unpkg.com/leaflet#1.3.3/dist/leaflet.js:5
_adjustPan self-hosted:891
_adjustPan self-hosted:844
or in my shiny app I get the following error:
Uncaught TypeError: Cannot set property '_leaflet_pos' of null
at Object.Lt [as setPosition] (eval at <anonymous> (jquery.min.js:2), <anonymous>:5:9959)
at e._adjustPan (eval at <anonymous> (jquery.min.js:2), <anonymous>:5:71149)
It would be really cool to have some sort of autopan for L.marker's used with supercluster. I know the user could just zoom out to drag the point to the intended location. This is not really an option in my case, because there are quickly too many points in the visible map extent which will slow down map panning (that's why I use the clustering in the first place).
I tried to center the leaflet map during the drag event using panTo
layer.on('drag',function(e){
//console.log('marker dragstart event');
var position=e.target.getLatLng();
map.panTo(new L.latLng(position.lat,position.lng));
});
but it seems that this somehow stops the drag event.
My question is: Is there a way to get an autopan for L.marker used with supercluster?
I set up a minimal reproducible example below. This example plots a single point (var marker) as well as three points (var markers) with supercluster. The single point can be dragged outside the visible map extent whereas the cluster points cannot.
var map = L.map('map').setView([51.505, -1.09], 9);
L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);
var marker = new L.marker([51.505, -1.09],{
draggable: true,
autoPan:true
}).bindPopup('autopan working').addTo(map);
// Empty Layer Group that will receive the clusters data on the fly.
var markers = L.geoJson(null, {
pointToLayer: createClusterIcon,
onEachFeature: onEachFeature
}).addTo(map);
var clusterData = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties":{
"id":0
},
"geometry": {
"type": "Point",
"coordinates": [-1.0,51.5]
}
},
{
"type": "Feature",
"properties":{
"id":1
},
"geometry": {
"type": "Point",
"coordinates": [-1.1,51.6]
}
},
{
"type": "Feature",
"properties":{
"id":2
},
"geometry": {
"type": "Point",
"coordinates": [-0.9,51.4]
}
}
]
}
function onEachFeature(f, layer) {
//add drag event
layer.on('drag',function(e){
//console.log('marker dragstart event');
//var position=e.target.getLatLng();
//map.panTo(new L.latLng(position.lat,position.lng));
});
layer.on('dragend',function(e){
//console.log('marker dragend event');
var changedPos = e.target.getLatLng();
//console.log('new location '+changedPos);
//load cluster again
clusterData.features[f.properties.id].geometry.coordinates[1]=changedPos.lat;
clusterData.features[f.properties.id].geometry.coordinates[0]=changedPos.lng;
index.load(clusterData.features);
update();
});
}
// Update the displayed clusters after user pan / zoom.
map.on('moveend', update);
function update() {
if (!ready) return;
var bounds = map.getBounds();
var bbox = [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()];
var zoom = map.getZoom();
var clusters = index.getClusters(bbox, zoom);
//console.log('clusters '+clusters);
markers.clearLayers();
markers.addData(clusters);
}
// Zoom to expand the cluster clicked by user.
markers.on('click', function(e) {
//console.log('check data' + e.layer.feature.properties.cluster_id);
var clusterId = e.layer.feature.properties.cluster_id;
var center = e.latlng;
var expansionZoom;
if (clusterId) {
expansionZoom = index.getClusterExpansionZoom(clusterId);
map.flyTo(center, expansionZoom);
}
});
var ready = false;
//load data
const index = new Supercluster({
radius: 150,
maxZoom:10
});
index.load(clusterData.features);
ready= true;
update();
function createClusterIcon(feature, latlng) {
if (!feature.properties.cluster){
return L.marker(latlng,{draggable:true,autoPan:true}); //add autoPan:true
}
var count = feature.properties.point_count;
var size =
count < 100 ? 'small' :
count < 1000 ? 'medium' : 'large';
var icon = L.divIcon({
html: '<div><span>' + feature.properties.point_count_abbreviated + '</span></div>',
className: 'marker-cluster marker-cluster-' + size,
iconSize: L.point(40, 40)
});
return L.marker(latlng, {
icon: icon
});
}
#map {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
<script src="https://unpkg.com/supercluster#7.1.0/dist/supercluster.min.js"></script>
<link href="https://cdn.rawgit.com/mapbox/supercluster/v4.0.1/demo/cluster.css" rel="stylesheet"/>
<script src="https://unpkg.com/leaflet#1.3.3/dist/leaflet.js"></script>
<link href="https://unpkg.com/leaflet#1.3.3/dist/leaflet.css" rel="stylesheet"/>
<div id="map"></div>
I am trying to add a popup to my map icons in Mapbox GL JS. So far I have been unsuccessful.
When I create a layer, in the layer's data, I have specified several properties. However when I try and add a popup to the icon, all of the properties are not present. Attempting to access them simply returns undefined.
Adding the layer:
function addRedAirports() {
map.addSource('hoggitRed', {
type: 'geojson',
cluster: true,
clusterMaxZoom: 14, // Max zoom to cluster points on
clusterRadius: 10, // Radius of each cluster when clustering points (defaults to 50)
data: redAirportArray[0]
});
map.addLayer({
"id": 'reds',
"type": "symbol",
"source": "hoggitRed",
"layout": {
"icon-image": "redIcon",
"icon-size": 0.075,
"icon-anchor": "bottom",
"icon-allow-overlap": true
}
});
Here is the contents of the data (redAirportArray[0]). I am looping through an api to get this data.
When I pass this data to mapbox, the properties are complete and correct. However when I try access them for a popup, I get undefined. Console logging the mapbox layer shows none of the inputted properties present..
(I have condensed this code slightly.. every loop I create a feature and then push it to the feature collection. I combined the two in this snippet for the sake of simplicity)
let redAirportArray = [{
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"properties": { //SETTING THE PROPERTIES
"test": 'test',
"ID": airportsRed[x].Id,
"team": airportsRed[x].Coalition
},
"geometry": {
"type": "Point",
"coordinates": [airportsRed[x].LatLongAlt.Long, airportsRed[x].LatLongAlt.Lat]
}
}]
Adding a popup on click
map.on('click', 'reds', function (e) {
var coordinates = e.features[0].geometry.coordinates.slice();
let team = e.features[0].properties.ID;
while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;
}
new mapboxgl.Popup()
.setLngLat(coordinates)
.setHTML(team)
.addTo(map);
});
Thanks in advance and I hope you can help!
With the way your layer is currently being added, you're looking for properties in the wrong location. e.features[0] is not defined since e is the feature you just clicked. Your pop up code should look something like this:
map.on('click', 'reds', function (e) {
var coordinates = e.geometry.coordinates.slice(); // Changed
let team = e.properties.ID; // Changed
while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;
}
new mapboxgl.Popup()
.setLngLat(coordinates)
.setHTML(team)
.addTo(map);
});
I'm currently using Mapbox GL JS and I have custom icons like this example and I want to be able to drag the icons.
I'm doing it similar to draggable point example where I have mouseDown, onMove, and onUp functions. However the part I'm stuck is in onMove, I'm not sure how to set the custom icons, which are div to update its positions throughout the dragging process. I'm updating the new coordinates (lng & lat) of the icons but I'm not sure how to actually move them as right now the icons won't move/drag.
In the original draggable point example it has map.getSource('point').setData(geojson); which updates the geojson to allow the point moving on the map.
So basically I just want to be able to drag the custom icons in Mapbox GL JS.
Thanks.
I ran into a similar problem and after many hours, managed to combine the two examples and export the coordinates to form fields. Try this snippet (use your own accessToken, map style and marker image)
$(document).ready(function() {
// ===============================================
// mapbox
// ===============================================
// Holds mousedown state for events. if this
// flag is active, we move the point on `mousemove`.
var isDragging;
// Is the cursor over a point? if this
// flag is active, we listen for a mousedown event.
var isCursorOverPoint;
mapboxgl.accessToken = '############# Add your own accessToken here ##########';
var map = new mapboxgl.Map({
container: 'map-one',
style: 'mapbox://############# Add your own style here ##########',
center: [5.037913, 52.185175],
pitch: 0,
zoom: 12
});
var nav = new mapboxgl.Navigation({
position: 'top-left'
});
map.addControl(nav);
var canvas = map.getCanvasContainer();
var geojson = {
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [5.067, 52.1923]
},
"properties": {
"title": "Afspreekpunt",
"marker-symbol": "dimmle-marker"
}
}]
};
function mouseDown() {
if (!isCursorOverPoint) return;
isDragging = true;
// Set a cursor indicator
canvas.style.cursor = 'grab';
// Mouse events
map.on('mousemove', onMove);
map.on('mouseup', onUp);
}
function onMove(e) {
if (!isDragging) return;
var coords = e.lngLat;
// Set a UI indicator for dragging.
canvas.style.cursor = 'grabbing';
// Update the Point feature in `geojson` coordinates
// and call setData to the source layer `point` on it.
geojson.features[0].geometry.coordinates = [coords.lng, coords.lat];
map.getSource('point').setData(geojson);
}
function onUp(e) {
if (!isDragging) return;
var coords = e.lngLat;
canvas.style.cursor = '';
isDragging = false;
// Update form fields with coordinates
$('#latitude').val(coords.lat);
$('#longitude').val(coords.lng);
}
// Mapbox map-accordion fix
$('#accordion').on('hidden.bs.collapse', function () {
map.resize();
})
$('#accordion').on('shown.bs.collapse', function () {
map.resize();
})
// After the map style has loaded on the page, add a source layer and default
// styling for a single point.
map.on('load', function() {
// Add a single point to the map
map.addSource('point', {
"type": "geojson",
"data": geojson
});
map.addLayer({
"id": "point",
"type": "symbol",
"source": "point",
"layout": {
// ##############################################
// NOTE: this is my marker, change it
// to the marker you uploaded in your map style
// - you will likely need different
// offset/translate values
// ##############################################
"icon-image": "my-marker",
"icon-size": 0.3,
"text-field": "",
"text-offset": [0, 0.6],
"text-anchor": "top",
"text-size": 14
},
"paint": {
"icon-translate": [-6, -34],
}
});
// If a feature is found on map movement,
// set a flag to permit a mousedown events.
map.on('mousemove', function(e) {
var features = map.queryRenderedFeatures(e.point, { layers: ['point'] });
// Change point and cursor style as a UI indicator
// and set a flag to enable other mouse events.
if (features.length) {
canvas.style.cursor = 'move';
isCursorOverPoint = true;
map.dragPan.disable();
} else {
//map.setPaintProperty('point', 'circle-color', '#3887be');
canvas.style.cursor = '';
isCursorOverPoint = false;
map.dragPan.enable();
}
});
// Set `true` to dispatch the event before other functions call it. This
// is necessary for disabling the default map dragging behaviour.
map.on('mousedown', mouseDown, true);
});
}); // end document ready
.map { border: 1px solid #ccc }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.25.1/mapbox-gl.js'></script>
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.25.1/mapbox-gl.css' rel='stylesheet' />
<div id='map-one' class='map' style='height: 360px;'></div>
<input id="latitude"> <input id="longitude">
Here is the working JSFiddle
what I need is to add image to rect polygon and also don't want it to repeat itself as when zooming in or out and want it to be fixed. any suggestions will be appreciated and if there is any other way to achieve so.
If it can be placed in geojson that will be great as I have to give some properties to each polygon. And create all the rect polygon dynamically.
code is below
var osmUrl = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
osmAttrib = '© OpenStreetMap contributors',
osm = L.tileLayer(osmUrl, {maxZoom: 18, attribution: osmAttrib});
var map = new L.Map('map', {layers: [osm], center: new L.LatLng(24, 121), zoom: 9});
var states = [{
"type": "Feature",
"properties": {"party": "Republican"},
"geometry": {
"type": "Polygon",
"coordinates": [[
[-104.05, 48.99],
[-96.58, 45.94],
[-104.03, 45.94],
[-104.05, 48.99]
]]
}
}, {
"type": "Feature",
"properties": {"party": "Democrat"},
"geometry": {
"type": "Polygon",
"coordinates": [[
[-109.05, 41.00],
[-102.03, 36.99],
[-109.04, 36.99],
[-109.05, 41.00]
]]
}
}];
var poly1 = [
[24, 121],
[24.5, 121],
[24.5, 121.9],
[24, 121.9]
];
L.polygon(poly1, {fill:'url(http://i.imgur.com/ktPFJKC.jpg)'}).addTo(map);
L.geoJson(states, {
style: function(feature) {
switch (feature.properties.party) {
case 'Republican': return {color:'#ff0000'};
case 'Democrat': return {color: "#0000ff"};
}
}
}).addTo(map);
The best thing to do here is use an ImageOverlay, which is designed for precisely this case. You can use the coordinates of your polygon object to create both the image overlay and an invisible GeoJSON layer that sits on top of it. If you are dynamically creating the polygon objects in the same format as your example poly1, then you can reference the indices of the corner points like this when creating the image overlay:
var imageUrl = 'http://i.imgur.com/ktPFJKC.jpg';
var imageBounds = L.latLngBounds([
poly1[0],
poly1[2]
]);
var imageLayer = L.imageOverlay(imageUrl, imageBounds).addTo(map).bringToBack();
The .bringToBack may be unnecessary if you always create the image before the GeoJSON polygon, but it does ensure that the image overlay doesn't interfere with other layer interactions. You can create a temporary GeoJSON object from the polygon object using .toGeoJSON, and set any GeoJSON properties you like:
var polyTemp = L.polygon(poly1).toGeoJSON();
polyTemp.properties.name = 'pineapple';
Then create an invisible L.GeoJSON layer to handle the interactions:
var boxOptions = {fillOpacity:0, opacity:0, onEachFeature: onEachBox};
var imageBox = L.geoJson(polyTemp, boxOptions).addTo(map);
function onEachBox(feature, layer) {
layer.bindPopup("Hello, I'm a " + polyTemp.properties.name);
}
The onEachBox function here is of course just an example to illustrate that you can access the GeoJSON properties. Here is an updated fiddle:
https://jsfiddle.net/17Ld98fv/