I'm using leaflet js to build a map with some pins https://leafletjs.com/ and I'm also allowing drawing of shapes, e.g. polygons, circles etc. I also these to be edited using a plugin called leaflet.pm https://github.com/codeofsumit/leaflet.pm.
There are events here but none of the events are giving be back the cordinates of the new position after edit mode has been disabled or after the drag has finished. This is the event I have hooked into;
map.on('pm:globaleditmodetoggled', function(e) {
console.log(e);
});
Wheres this event gives me what is required;
map.on('pm:create', function(e) {
let obj = {
type: e.shape,
coordinates: e.layer.editing.latlngs[0][0]
};
$('#cords').val(JSON.stringify(obj))
});
Any ideas how when the shape is edited that I can get the update coordinates?
I'm Sumit, the maintainer of leaflet.pm
What you can do is: listen to an event being created and add the edit event to the new shapes:
map.on('pm:create',(e) {
e.layer.on('pm:edit', ({ layer }) => {
// layer has been edited
console.log(layer.toGeoJSON());
})
});
Of course, whenever you add a layer to the map you can also apply the pm:edit event to its reference.
Additionally, when you create layers or add layers to the map, you can simply store the reference. When editing is done you can just check the reference for it's coordinates (just as you would normally in leaflet). If you just need to know when editing is done, use the pm:edit event to catch whenever a layer was edited.
Hope this helps 👍
Related
The closest to my question are: Mapbox gl js - overlapping layers and mouse event handling, Mapbox add popup on hover (layer), close on mouseleave, but keep open on popup hover and How to ignore mouse events on a mapbox layer - but they don't answer it.
I have two layers, let's imagine one is for a country, and another is for a city. Both of them a handled like this:
// CountryLayer.js
map.on("mousemove", "country-layer", e => {
// show info about the country
featureProps = e.features[0] // display some notification from the props
...
// CityLayer.js
map.on("mousemove", "city-layer", e => {
// show info about the city
featureProps = e.features[0] // display some notification from the props
...
It's done in different components. But when I mouseover city-layer mapbox thinks that I'm still "mousemoving" on top of the country-layer as well, so I get two notifications from separate components, where I need only one - in that case from the city-layer cause it's on top of country-layer.
Handling the mousemove without layerId in one place is gonna be a mess and breaks all the good rules about programming. Creating external "event manager" which will track whether I'm hovering the city and if is so will remove mousemove event from country-layer - is complex. I didn't find any good alternatives. At least, I would be glad to disable pointer events for a layer like this:
map.on("mousemove", "city-layer", e => {
map.getLayer("country-layer").setStyle({ "pointer-events": false })
featureProps = e.features[0]
...
or something like this. Is it possible? Is there more adequate way around it?
e.originalEvent.stopPropagation(); does not work
It appears, that you can do e.originalEvent.preventDefault(); in the city-layer and add a check e.originalEvent.defaultPrevented in the country-layer from the example.
However, I have issues with z-index position of layers: map.moveLayer("city-layer", "country-layer"); or vice-vise doesn't actually change the way, event propogates, so for some reason my country-layer always come first, so when it checks for e.originalEvent.defaultPrevented, preventDefault() wasn't fired yet, so it comes always false.
But technically, this answers my question.
map.on can listen on multiple layers at once. With events like mousemove it returns features in reverse layer order (from "highest" to "lowest").
So the way to do this is:
map.on("mousemove", ["city-layer", "country-layer"], e => {
feature = e.features[0]
if (feature.layer.id === 'city-layer') {
// ...
} else {
// ...
}
});
I have been trying to close an info bubble when the map only is clicked, but I can't achieve it.
I have tried the following
this.map.addEventListener("tap", this.handleMapClick);
private handleMapClick(evt: Event) {
this.clearOpenInformationBubble();
}
However, the tap event is triggered even when maps objects are clicked meaning that the info bubble remains closed when I click a marker.
I have also tried to add a 'blur' event listener to the bubble element, but that doesn't seem to work
const bubble = new H.ui.InfoBubble(evt.target.getGeometry(), {
content: ...
});
this.ui.addBubble(bubble);
bubble.getElement().focus()
bubble.getElement().addEventListener('blur', evt => {
this.clearOpenInformationBubble();
})
I was wondering if there is a way to listen for an event triggered by a map ONLY tap.
Here's a similar implementation in Google maps.
google.maps.event.addDomListener(map, "click", function() {
alert('Map clicked')
});
I think I'd try the answer here. In this answer, they wanted to close any other open infobubbles first. I believe if you remove the event listener on your group, keep the one on your map, and always remove open infobubbles you would be good. Then add logic to see if the event target value has data and show an infobubble.
This assumes your doing infobubbles based on markers having a data object associated with them.
Edit:
I was able to get this to work - again assuming your use case is markers to infobubbles.
map.addEventListener('tap', evt => {
ui.getBubbles().forEach(bub => ui.removeBubble(bub));
if(!evt.target.getData) return;
// for all objects that it contains
var bubble = new H.ui.InfoBubble(evt.target.getGeometry(), {
// read custom data
content: evt.target.getData()
});
// show info bubble
ui.addBubble(bubble);
});
You can check the event target in the callback function.
If it is the map, then only you close the InfoBubble:
this.map.addEventListener("tap", this.handleMapClick);
private handleMapClick(evt: Event) {
if (evt.target === this.map) {
this.clearOpenInformationBubble();
}
}
```
I would like to prevent a user from creating more than one shape on the map. For example if the user creates a polygon, then all shape icons on the toolbar should be disabled. When the user deletes the previously created shape, then icons on the toolbar should get enabled.
How can I do this? I tried removing the toolbar on the draw:created event and adding a new toolbar on draw:deleted event but it lead to errors (see attached screenshot).
Leaflet enables us to remove and add the toolbars with remove() and addTo() methods.
What you need to do is to create two toolbars. One is a default L.Control.Draw and the other one is without the 'draw' component:
self.drawControlFull = new L.Control.Draw();
self.drawControlEdit = new L.Control.Draw({
edit: {
featureGroup: editableLayers,
edit: false
},
draw: false
});
map.addControl(drawControlFull);
Then you just listen to the draw:created and draw:deleted events and you add/remove them as needed:
map.on('draw:created', function(e) {
var type = e.layerType,
layer = e.layer;
self.drawControlFull.remove();
self.drawControlEdit.addTo(map);
editableLayers.addLayer(layer);
});
map.on('draw:deleted', function (e) {
self.drawControlEdit.remove();
self.drawControlFull.addTo(map);
});
This solution maybe doesn't cover all the use cases but it's just an example. I have also created a jsFiddle for this so you can see how it works.
I am looking at the Data Maps library of D3, and I would like to know how to go about manipulating the svg content. Let's say I look at the basic world map, how would I run a alert("Selected") by clicking on Canada? Or change its background color by clicking on it?
EDIT: In my specific instance, I am using the US map. I use the following lines to boot up the map:
var map = new Datamap({element: document.getElementById('maincontains'),
scope: 'usa',
fills: {defaultFill: 'rgb(217, 217, 217)'}
});
Now, in the usa.js I find IDs such as "NY". However, this does not allow me to
document.getElementById("NY").addEventListener('click', function(){
alert("New York");}
The documentation on Events says the following:
All events are bubbled up to the root svg element and to listen to
events, use the done callback.
So in order to bind a click event and alert the name, use the done event of the map object and use the svg object inside the event handler:
<script>
var map = new Datamap({
element: document.getElementById('container'),
done: function(datamap) {
datamap.svg.selectAll('.datamaps-subunit').on('click', function(geography) {
alert(geography.properties.name);
});
}
});
</script>
My example click on Greenland:
Further restrictions (e.g. only specific countries) could be made using the properties.
So I'm having a problem generating my jVectorMap.
The map itself sits inside a very custom drop down menu that I have created and this is where I suspect the problem is.
When I mouseover my menu item to open up the drop down which contains the map the actual svg starts out with a forced dimension of 100px x 100px.
What I have tried to do a number of workarounds wher I call the "map.setSize()" either on the mouseclick event of the dropdown as well as the mouseover event of the container itself. The problem here is my dropdown is not subject to a click event but shows on the mouseover event. However, at the point of the mouseover event the actual container for the map hasn't loaded so I'm still stuck with a 100px x 100px svg.
To get around this I've put an event on the mouseover event of the container itself but this isn't great either as it then requires the user to move his mouse over the container before it actually shows the map, something I don't want to happen.
Is there a way of getting the map built inside a div which is invisible before my menu event occurs?
For an example of my problem I've created this at jsfiddle http://jsfiddle.net/AEup9/
You will notice that when you hover over the "Show Map" menu item (the only item) the drop down is blank except for the topic headers until you move the mouse over the actual drop down itself which then reloads the map. I then keep the map there by using my "loaded" variable created before my mouseover event and a force map.setSize() inside the same event:
var loaded = false;
$('#aamap').mouseover(function () {
if (!loaded) {
(function () {
map = new jvm.WorldMap({
map: 'za_mill_en',
container: $('#southafrica-map'),
backgroundColor: '#cbd9f5',
initial: {
fill: 'white'
},
series: {
regions: [{
attribute: 'stroke'
}]
}
});
loaded = true;
})();
}
map.setSize();
});
This is my rough work around but not what I really want as I want the map to show up first time.
Can anyone help me here?
Edit: I finally decided to NOT go ahead with using jvectormap due to this issue. Instead I opted to use jqvmap which is to some degree a fork of jvectormap, however the issues experienced with jvectormap were no longer a problem.
I met this issue as well.
To solve that problem we need to run an updateSize method on a map object when container of our map becomes visible. First, to get the map object we need to use this command:
$('#world-map').vectorMap('get', 'mapObject') and when execute updateSize on it, like:
var map = $('#world-map').vectorMap('get', 'mapObject');
map.updateSize();
or in a shorter form:
$('#world-map').vectorMap('get', 'mapObject').updateSize();