I am using Leaflet JS to draw polygons on a map.
I am able to draw the shapes, and to save the shapes to a database as GeoJSON like this:
.ts file
...
let layer = event.layer;
let geometry = layer.toGeoJSON();
this.drawLayer.addLayer(layer);
}
I call a method in the ngOnInit method to redraw the shapes:
drawPolygonShape(data) {
if (data.polygon.geometry) {
let shape = {
type: data.polygon.type,
geometry: {
type: data.polygon.geometry.type,
coordinates: data.polygon.geometry.coordinates
},
properties: {}
};
L.geoJSON(shape, {
onEachFeature: this.onEachFeature
}).addTo(this.myMap);
}
}
...
onEachFeature(feature, layer) {
layer.on('click', event => {
// layer.bindPopup('Hello World'); // Works great
let popover = this.popoverCtrl.create('MyComponent', { layer: layer });
popover.present();
});
}
I am importing MyComponent into the module file, so I know it is available. However, I am always getting the following error message:
Cannot read property 'create' of undefined
So, somewhere there is a timing or scope problem that is not correctly working with the click event.
It seems like a scope problem, as any component gives the same error. Any suggestions are greatly appreciated!
EDIT
Thank you to #ghybs I've tried adding this as a third argument to the click event, but I'm still getting the same error:
onEachFeature(feature, layer) {
layer.on('click', event => {
// layer.bindPopup('Hello World'); // Works great
let popover = this.popoverCtrl.create('MyComponent', { layer: layer });
popover.present();
}, this);
}
EDIT 2
Thank you #ghybs!
I'm curious about your second suggestion - I am setting up the map like this:
let mapOptions: any = {
position: 'topright',
draw: {
polyline: false,
...
}
}
...
let drawControl = new L.Control.Draw(mapOptions);
this.myMap.addControl(drawControl);
this.myMap.on(L.Draw.Event.CREATED, this.onCreatePolygon.bind(this));
The .bind(this) makes more sense now - onCreatePolygon is a method I am using to save the shape.
How do you suggest I delegate to the click event for each shape?
this.myMap.on(L.Draw.Event.CLICK, this.MYCLICKHANDLER.bind(this));
(it's obvious I'm not familiar working with this so I appreciate all of your time.)
In your case you have a double context issue since you also pass your onEachFeature method that will get invoked with a this context that is already different from your class instance.
You could do onEachFeature: this.onEachFeature.bind(this) together with your layer.on("click", cb, this)
You could also use event delegation by attaching the listener on your GeoJSON Layer Group instead of each individual feature, so that you get rid of one of the context:
var geoJsonData = {
"type": "Point",
"coordinates": [2.35, 48.86]
};
class Component {
constructor(mapId) {
this.myMap = L.map(mapId).setView([48.86, 2.35], 11);
L.geoJSON(geoJsonData).addTo(this.myMap).on("click", event => {
console.log(event.target); // this is the GeoJSON Layer Group.
console.log(event.layer); // this is the individual child feature.
let popover = this.popoverCtrl.create('MyComponent');
popover.present();
}, this); // Not even needing the 3rd arg since you use a fat arrow function, which does not have its own context.
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(this.myMap);
}
}
Component.prototype.popoverCtrl = { // Dummy popoverCtrl
create(content) {
dummyPopoverContent = content;
return {
present() {
alert(dummyPopoverContent);
},
};
},
};
var dummyPopoverContent = '';
new Component('map');
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.3.1/dist/leaflet.css" integrity="sha512-Rksm5RenBEKSKFjgI3a41vrjkw4EVPlJ3+OiI65vTjIdo9brlAacEuKOiQ5OFh7cOI1bkDwLqdLw3Zg0cRJAAQ==" crossorigin="" />
<script src="https://unpkg.com/leaflet#1.3.1/dist/leaflet-src.js" integrity="sha512-IkGU/uDhB9u9F8k+2OsA6XXoowIhOuQL1NTgNZHY1nkURnqEGlDZq3GsfmdJdKFe1k1zOc6YU2K7qY+hF9AodA==" crossorigin=""></script>
<div id="map" style="height: 180px"></div>
See also start a function when click on circle - leaflet
As for your last question, with the 1st remark above you could re-write:
this.myMap.on(L.Draw.Event.CREATED, this.onCreatePolygon.bind(this));
into:
this.myMap.on(L.Draw.Event.CREATED, this.onCreatePolygon, this);
Event delegation does not seem relevant in this case since you already attach a single listener on your map, not on individual layers.
Related
Using Leaflet, I linked to a L.easyButton a function which adds a geoJson polygon layer to a map, and that activates different functions "onEachFeature" of this layer. Among them, a function when clicking on a feature which creates a separate geoJson polygon layer with this feature and add it to a specific LayerGroup. What I would like is that after clicking on a feature, the different functions "onEachFeature" would not be activated anymore and that the user would need to push the L.easyButton again if he/she wants to do that another time. Everything works fine, except the fact to deactivate the "onEachFeature" functions. I tried to use map.removeControl(), map.removeLayer() and delete the variable directly, but none of these options work.
My code is:
L.easyButton( '<strong>Sélectionner une commune</strong>', function(){
var communes_geojson = $.getJSON("data/Adm/Adm_Communes_4326.geojson",function(communes){
var communes_new = L.geoJson(communes, {
style: stylelayer.communes,
onEachFeature: function addMyData (feature,layer){
layer.on({
click: selectcommune
});
}
});
map.addControl(communes_new);
});
info.addTo(map);
},
{position: 'topright'}).addTo(map);
function selectcommune(e) {
var layer=e.target;
var feature = e.target.feature;
var selectcommune1 = L.geoJson(feature, {
style: stylelayer.highlight
}).addTo(map);
control_map2.addOverlay({
name:feature.properties.NameFRE,
layer:selectcommune1
});
// don't work
// map.removeControl(communes_geojson);
// map.removeControl(communes_new);
// map.removeControl(layer);
// map.removeLayer(communes_geojson);
// map.removeLayer(communes_new);
// map.removeLayer(layer);
// delete communes_geojson;
// delete communes_new;
// delete layer;
Tahnk you for your help.
In selectcommune, once you do what you want (style your feature and add your overlay), you then need to go back and void out the onEachFeature function that you had added with the L.easyButton:
L.easyButton( '<strong>Sélectionner une commune</strong>', function(){
var communes_geojson = $.getJSON("data/Adm/Adm_Communes_4326.geojson",function(communes){
var communes_new = L.geoJson(communes, {
style: stylelayer.communes,
onEachFeature: function addMyData (feature,layer){
layer.on({
click: e => selectcommune(e, communes_new)
});
}
});
map.addControl(communes_new);
});
info.addTo(map);
},
function selectcommune(e, data) {
var layer=e.target;
var feature = e.target.feature;
var selectcommune1 = L.geoJson(feature, {
style: stylelayer.highlight
}).addTo(map);
control_map2.addOverlay({
name:feature.properties.NameFRE,
layer:selectcommune1
});
data.eachLayer( layer => layer.on = () => {} )
}
So you hit the easybutton, and layer.on is set for each feature to fire the selectcommune. When it fires, it removes the layer.on function (or overwrites it to do nothing), until you hit the easybutton again.
I am trying to highlight an entire layer group using JavaScript from code elsewhere on the page. My group is set up like this:
var annexa_building = new L.Polygon([
[68.23682, -47.46094],[68.3668, -40.07812]...
], {'label': popup_annexa, 'popup': content_annexa}).addTo(map);
var annexb_building = new L.Polygon([
[68.82387, -29.729],[69.24837, -22.41211]...
], {'label': popup_annexb, 'popup': content_annexb}).addTo(map);
var academics_group = new L.LayerGroup([
annexa_building,
annexb_building
]);
I tried to find documentation on calling an effect with the group referenced, but couldn't. What I need is to be able to call the highlight code for both layers at the same time, like this:
setHighlight(academics_group);
I also tried doing something like this (calling individual layers one at a time):
<img src="images/1.png" class="img-swap" onclick="setHighlight('annexa_building')" />
And this:
jQuery(function(){
$(".img-swap").on('click', function() {
if ($(this).attr("class") == "img-swap") {
this.src = this.src.replace("_off","_on");
setHighlight("annexa_building");
} else {
this.src = this.src.replace("_on","_off");
}
$(this).toggleClass("on");
});
});
But it reports back that the layer.setStyle is not a function.
The function setHighlight(layer) does work fine within the map.
function setHighlight (layer) {
if (highlight) {
unsetHighlight(highlight);
}
layer.setStyle(style.highlight);
highlight = layer;
}
An ideas on a solution for this? Thanks!
According to the documentation the LayerGroup hasn't the setStyle() function. You should use FeatureGroup that is an extension of the LayerGroup and has the setStyle() function.
Also you should call .addTo(map) on the entire group instead of each layer. This way if you add new layers to the group they will be automatically added to the map.
I have a single page application, using reactjs and react-router. I developed a maps component, and of course with google maps have a memory leak going from screen to screen if I try and cleanup the map on component did mount and rebuild it on component did mount. so I tried this approach:
What is the proper way to destroy a map instance
and re-using the map instance and the node combined with excessive measures to clean things up works well and stabilizes things. For cleanup, I
call clearInstanceListeners on every marker, map, window and document
set map to null on any markers
delete this.map on the component then set it to null
delete this.markers then set it to empty array
The issue i'm having at this point is that when I trigger resize, it's not resizing. after reading several other posts, and making sure i'm triggering after the div is shown, it doesn't resize. However, I have a resize function bound to window.onresize, and have noticed that after resizing the window, and the second time the function is called, the map does resize and center. here's that function:
sizeMapToContainer: function () {
google.maps.event.trigger(this.map, 'resize');
if (this.props.fitBounds) {
this.zoomInOnMarkers();
} else {
this.map.setCenter(new google.maps.LatLng(this.props.center[1], this.props.center[0]));
this.map.setZoom(this.props.zoom);
}
},
and the function for window resize:
window.onresize = function onResize () {
if (this.map) {
this.sizeMapToContainer();
}
}.bind(this);
My render call for react just returns a div, where later on in did mount I append the map div myself.
buildMap: function (nextProps) {
var mapProps;
if (nextProps) {
mapProps = nextProps;
} else {
mapProps = this.props;
}
var centerpoint = mapProps.center;
var zoom = mapProps.zoom || 8;
var center = new google.maps.LatLng(centerpoint[1], centerpoint[0]);
var mapNode = this.refs.map.getDOMNode();
var mapOptions = {
center: center,
zoom: zoom
};
if (mapInstance.map) {
window.tmMap = this.map = mapInstance.map;
//check if the nodes are already there
if (!mapNode.hasChildNodes()) {
mapNode.appendChild(mapInstance.node);
}
this.map.setOptions(mapOptions);
} else {
mapInstance.node = document.createElement('div');
mapInstance.node.classList.add('full-height');
mapInstance.map = new google.maps.Map(mapInstance.node, mapOptions);
window.tmMap = this.map = mapInstance.map;
mapNode.appendChild(mapInstance.node);
this.map.setOptions(mapOptions);
}
this.buildMarkers();
this.setMarkers();
this.bindMarkers();
google.maps.event.addListener(this.map, 'idle', this.onIdle);
},
This component is a nested component.
Please let me know if there is any other code that will help. I am of course using the latest google maps api, using it directly.
So i'm making this application with leafet.js.
This application requires that i have to manually draw grids onto the screen,
that i've taken care in a draw_grid() function that draws a bunch of polygons to the screen.
i have this function that i'm calling to trigger the change of the leaflet map.
zoom - the zoom integer and size is a dict like {x:1,y:1} that controls the size of the tiles drawn onto the map. (they need to change as the units the tiles are being drawn under are lat,long points on the map.
function changeZoom(zoom,size){
map.setZoom(zoom);
setSize(size);
setTimeout(drawGrid,500)s;
}
the reason i have to use the setTimeout is because the leaflet ignores any drawing commands onto the map (which i'm doing as a layer) until the map has finished animating.
how to do this asynchronously instead?
You can use the map.zoomend event, described in the API here
map.on('zoomend', function() {
drawGrid();
});
Once the map finishes the zooming animation, it will then call the drawGrid function.
In newer version of Leaflet, "zoomed" is no longer an event. There are now "zoomstart" and "zoomend" events:
map.on("zoomstart", function (e) { console.log("ZOOMSTART", e); });
map.on("zoomend", function (e) { console.log("ZOOMEND", e); });
This is best way to managed leflet Zoom control clicked
/*Zoom Control Click Managed*/
var bZoomControlClick = false;
mymap.on('zoomend',function(e){
var currZoom = mymap.getZoom();
if(bZoomControlClick){
console.log("Clicked "+currZoom);
}
bZoomControlClick = false;
});
var element = document.querySelector('a.leaflet-control-zoom-in');
L.DomEvent.addListener(element, 'click', function (e) {
bZoomControlClick = true;
$(mymap).trigger("zoomend");
});
var element1 = document.querySelector('a.leaflet-control-zoom-out');
L.DomEvent.addListener(element1, 'click', function (e) {
bZoomControlClick = true;
$(mymap).trigger("zoomend");
});
I have an OpenLayers map that draws features in a vector layer. The features are selectable and have a popup on select. Unfortunately in a lot of cases the features overlap so it can be impossible to select some features. I think that what I need to do to fix this is change my select control so that it uses a click handler and searches the map for features at this point. What sort of function do i need to write? are there any examples of this being implemented before?
This is how the features are drawn:
var vector_Layer = new OpenLayers.Layer.Vector();
function GetFeaturesFromKMLString (strKML) {
var format = new OpenLayers.Format.KML({
'internalProjection': new OpenLayers.Projection("EPSG:900913"),
'extranalProjection': new OpenLayers.Projection("EPSG:4326")
});
return format.read(strKML);
};
vector_Layer.addFeatures(GetFeaturesFromKMLString('$newkml'));
And this is how Layers are currently selected:
var select = new OpenLayers.Control.SelectFeature(vector_Layer, {clickout: true});
vector_Layer.events.on({
"featureselected": onFeatureSelect,
"featureunselected": onFeatureUnselect});
map.addControl(select);
select.activate();
select.handlers['feature'].stopDown = false;
select.handlers['feature'].stopUp = false;
here is the click event listener I was planning on using:
OpenLayers.Control.Click = OpenLayers.Class(OpenLayers.Control, {
defaultHandlerOptions: {
'single': true,
'double': false,
'pixelTolerance': 0,
'stopSingle': false,
'stopDouble': false
},
initialize: function(options) {
this.handlerOptions = OpenLayers.Util.extend(
{}, this.defaultHandlerOptions
);
OpenLayers.Control.prototype.initialize.apply(
this, arguments
);
this.handler = new OpenLayers.Handler.Click(
this, {
'click': this.onClick
}, this.handlerOptions
);
},
onClick: function(evt) {
//function that seachers for and selects features at this point
},
As the vector features use the OpenLayers.Geometry classes as geometry description you should have a look at Geometry, there is an intersects method which may be useful:
See: http://dev.openlayers.org/releases/OpenLayers-2.13/doc/apidocs/files/OpenLayers/Geometry/Point-js.html
You will also need to intercept the click event in order to run intersection checks.