Adding L.Control.Draw to Leaflet manually - leaflet

I'm trying to add a draw control to my leaflet map like this:
const drawnItems = L.featureGroup().addTo(map)
map.addControl(new L.Control.Draw({
edit: {
featureGroup: drawnItems
}
}))
But I always get this error in the _initModeHandler method of leaflet.draw.js
TypeError: Cannot set property 'polyline' of undefined
Seems like this._modes[type] = {}; fails because this._modes is undefined.
I tried to make this._modes an object if undefined but this just threw another error anbout how an addToolbar method is undefined, too.

The error came from another L.Toolbar class that did override the one supplied by Leaflet.draw.
The other L.Toolbar didn't set this._modes in initialize()

Related

ERROR TypeError: Cannot read property '_leaflet_pos' of undefined

I have angular 10 app using ngx-leaflet and routing. I have a map component, which dynamically displays custom markers on map, based on user selection. I navigate from map component view to another component. Then I navigate back to map component. User can change date, and based on that, old layer of markers is removed and new layer of markers is loaded and shown. Everything works fine, but I always get this error:
ERROR TypeError: Cannot read property '_leaflet_pos' of undefined
at getPosition (leaflet-src.js:2450)
at NewClass._getMapPanePos (leaflet-src.js:4439)
at NewClass._moved (leaflet-src.js:4443)
at NewClass.getCenter (leaflet-src.js:3798)
at NewClass.setZoom (leaflet-src.js:3181)
at SafeSubscriber._next (map.component.ts:960)
at SafeSubscriber.__tryOrUnsub (Subscriber.js:183)
at SafeSubscriber.next (Subscriber.js:122)
at Subscriber._next (Subscriber.js:72)
at Subscriber.next (Subscriber.js:49)
I can reproduce this error only when I go back to the map component. If i stay only at the map component no error is shown. I've searched for fix, but from what I found it seems nobody really knows why is this happening and how to fix this error. I've found these two issues on GitHub dealing with the same problem in Vue.js, so I guess it's problem with leaflet itself, and not ngx-leaflet.
https://github.com/vue-leaflet/Vue2Leaflet/issues/613
https://github.com/stefanocudini/leaflet-search/issues/129
I've tried to change this:
function getPosition(el) {
// this method is only used for elements previously positioned using setPosition,
// so it's safe to cache the position for performance
return el._leaflet_pos || new Point(0, 0);
}
to this:
function getPosition(el) {
// this method is only used for elements previously positioned using setPosition,
// so it's safe to cache the position for performance
if(el){
return el._leaflet_pos || new Point(0, 0);
}
else{
return new Point(0, 0);
}
}
But then the error just looks like this:
ERROR TypeError: Cannot set property '_leaflet_pos' of undefined
at setPosition (leaflet-src.js:2433)
at NewClass._resetView (leaflet-src.js:4154)
at NewClass.setView (leaflet-src.js:3174)
at NewClass.setZoom (leaflet-src.js:3186)
at SafeSubscriber._next (map.component.ts:960)
at SafeSubscriber.__tryOrUnsub (Subscriber.js:183)
at SafeSubscriber.next (Subscriber.js:122)
at Subscriber._next (Subscriber.js:72)
at Subscriber.next (Subscriber.js:49)
at MapSubscriber._next (map.js:35)
UPDATE:
As #pk. suggested in comments ,when I don't call setZoom or call it before I remove old markers I get this error:
ERROR TypeError: Cannot read property 'appendChild' of undefined
at NewClass._initIcon (leaflet-src.js:7608)
at NewClass._initIcon (leaflet.rotatedMarker.js:23)
at NewClass.onAdd (leaflet-src.js:7460)
at NewClass._layerAdd (leaflet-src.js:6572)
at NewClass.whenReady (leaflet-src.js:4433)
at NewClass.addLayer (leaflet-src.js:6634)
at NewClass.eachLayer (leaflet-src.js:6861)
at NewClass.onAdd (leaflet-src.js:6845)
at NewClass._layerAdd (leaflet-src.js:6572)
at NewClass.whenReady (leaflet-src.js:4433)
UPDATE 2:
When I don't add new marker layer to map (this.deskLayer.addTo(map)), the error dissapears, but I want to add new markers to map...
This is what happens when user changes date:
onMapReady(map: Map) {
//listening for USER DATE CHANGE
this.userFloorService.sharedUserSelectedDate
.pipe(skip(1))
.subscribe(() => {
this.deskLayer.remove(); // first remove old desks
this.userFloorService // then get desks and reservations
.getFloor(this.floorNumber)
.subscribe( (data) => {
// create new reservations
let reservationsArr = data.records[0].reservations;
// create new DESKS
let deskMarkers = [];
data.records[0].desks.forEach((desk) => {
let deskId = desk.desk_id;
let deskMarker = marker(
[
desk.desk_coordinate.coordinates[0],
desk.desk_coordinate.coordinates[1],
],
{
title: this.getDeskTitle(desk, reservationsArr), // set desk title to RESERVED/FREE
rotationAngle: desk.desk_angle,
rotationOrigin: 'center center',
riseOnHover: true
}
).on('click', () => {
this.zone.run(() => {
this.openDeskDialog(deskMarker.options.title,deskId);
});
});
deskMarker.setIcon(this.getDeskIcon(deskMarker)); // for displaying desk icons on zoomLvl -1.5
deskMarkers.push(deskMarker);
});
this.deskLayer = layerGroup(deskMarkers); // add new desks to deskLayer
this.layersControl.overlays['Desks'] = this.deskLayer; // reassign desks in overlays for correct desk layer toggling
this.deskLayer.addTo(map);
map.setZoom(-1); // set zoom
},
error =>{
console.log(error);
}
);
});
It turned out, that these errors were happening because I used BehaviorSubject to pass data between components, and everytime I navigated out and back to the map component, new subscription to BehaviorSubject was created without destroying the old subscribtion. So destroying subscriptions everytime I navigated from map component solved it. Maybe this will help to somebody.

Click on Leaflet map closes modal, click on marker opens modal

My Leaflet map has markers that open modals.
When a user clicks on the map, however, I would like for the modal to close. But the bit of code that makes that happen (below) interacts with the marker, and forces it to close as soon as it opens:
map.on('click', function(e) {
$('.modal').modal('hide'); });
I did get this to work—see the JSFiddle here: https://jsfiddle.net/askebos/Lh1y12uq/
But as you can see, the only reason it seems to be working is because it creates the following error:
Uncaught TypeError: e.preventDefault is not a function.
I imagine it's because the map.on('click'...) function is prevented from executing.
Any thoughts on how I can get to the same behaviour without the error?
The solution is to add an init() function that keeps track when a marker is clicked. Inspiration came from this question.
First, add the init() function to your code:
function init() {
init.called = true;
}
Then call the function when the marker is clicked:
function markerOnClick(e) {
init();
...
}
Make a function that fires when the map is clicked, but include an if/else statement that checks whether init.called has been set to true. If this is the case, reset init.called. If it has not been set to true, then the map was clicked elsewhere and any modals may close.
function mapClick () {
if(init.called) {
init.called = false;
}
else{
$('.modal').modal('hide');
}
}
Finally, bind the mapClick function to map clicks.
map.on('click', mapClick);
The function will no longer override marker clicks, and the error has been resolved as well. That still doesn't tell me why e.preventDefault caused an error, so any explanations would be welcome!
A working JSFiddle can be found here: https://jsfiddle.net/askebos/oesh59jr/

Leaflet/Mapbox error - "Cannot read property 'minZoom' of undefined"

I'm creating a map using leaflet with the following code:
L.mapbox.accessToken = 'pk.##MY TOKEN HERE##';
var map = L.mapbox.map('map', 'mapbox.streets');
map.scrollWheelZoom.disable();
map.setView(new L.LatLng(32.75, -97.33), 10);
But when the page loads, I get a console error of
Cannot read property 'minZoom' of undefined
This error originates from the line that says:
var map = L.mapbox.map('map', 'mapbox.streets');
I've tried to set the minZoom value after the map variable is declared, but it doesn't work b/c by that point the error has already occurred. I've already tried to set the setView in the same map declaration line, but it didn't help either.
Has anybody ran across this error before?
try this:
var map = L.mapbox.map('map', 'mapbox.streets').setView([0, 0], 2);
source: Mapbox blog
Since it can't read the property, it looks like it's having problems finding a <div> with an id of map.
In your HTML page, try adding <div id="map"></div> in the body where you want your map to appear.

Unable to add marker to Angular Leaflet directive

I'm trying to add a marker to the Leaflet directive but somehow it doesn't accept markers created with the default L.marker() method.
The directive is used as follows:
<leaflet markers="markers" center="center" layers="layers" defaults="defaults"></leaflet>
I'm extending my $scope in the controller as prescribed:
angular.extend($scope, {
markers: {},
//markers: { { someName: {lat:52.163815,lng:5.365131} } //this does work
});
Adding a marker afterwards doesn't work somehow. First the .markers object is not an array, so I can't just add any elements using push(). But even adding an element as associative array doesn't work:
var marker = L.marker(L.latLng(52.163815, 5.365131));
$scope.markers[0] = marker;
The error is:
[AngularJS - Leaflet] The marker definition is not valid.
[AngularJS - Leaflet] Received invalid data on the marker 0.
I'm overlooking something very simple but I've got no idea what... Any lead would be greatly appreciated.
$scope.markers is expecting to get an object with marker properties, not the marker itself. What would work in your example is just a LatLng object, before wrapping it as Marker.
$scope.markers[0] = L.latLng(52.163815, 5.365131);
Or, if you get Markers from the outside, you can get it back from the inside:
$scope.markers[0] = marker.getLatLng();
Obviously, that doesn't convey another markers' properties, just coordinates.

Find out if a leaflet control has already been added to the map

I wrote a custom Leaflet control. It's some kind of legend that may be added for each layer. The control itself has a close button to remove it from the map (like a popup).
The control can be added by clicking a button.
My problem is that the user may add the same control to the map several times. So what I need is to test if this specific control has already been added to the map and, if so, don't add it again.
I create a control for each layer, passing some options
var control = L.control.customControl(mylayer);
and add it to my map on button click
control.addTo(map);
Now imagine the control has a close button and may be closed. Now if the user clicks the button again, I only want to add the control if it's not already on the map - something like this (hasControl is pseudocode, there is afaik no such function)
if(!(map.hasControl(control))) {
control.addTo(map);
}
For simplicity I made an example where I create a zoom control and add it twice here.
Easiest way is to check for the existence of the _map property on your control instance:
var customControl = new L.Control.Custom();
console.log(customControl._map); // undefined
map.addControl(customControl);
console.log(customControl._map); // returns map instance
But please keep in mind, when using the _map property, that the _ prefix of the property implies that it's a private property, which you are normally not supposed to use. It could be changed or removed in future versions of Leaflet. You're not going to encounter that if you use the follow approach:
Attaching a reference of your custom control to your L.Map instance:
L.Control.Custom = L.Control.extend({
options: {
position: 'bottomleft'
},
onAdd: function (map) {
// Add reference to map
map.customControl = this;
return L.DomUtil.create('div', 'my-custom-control');
},
onRemove: function (map) {
// Remove reference from map
delete map.customControl;
}
});
Now you can check for the reference on your map instance like so:
if (map.customControl) { ... }
Or create a method and include it in L.Map:
L.Map.include({
hasCustomControl: function () {
return (this.customControl) ? true : false;
}
});
That would work like this:
var customControl = new L.Control.Custom();
map.addControl(customControl);
map.hasCustomControl(); // returns true
map.removeControl(customControl);
map.hasCustomControl(); // returns false
Here's a demo of the concept on Plunker: http://plnkr.co/edit/nH8pZzkB1TzuTk1rnrF0?p=preview