Leaflet error when zooming after closing popup - javascript

I already saw the same question here: StackOverflow
But none of the answers helped or atleast I didn't understand it.
If I place a new marker on the map by clicking and then remove the marker by clicking again and then attempt to zoom, I get the error:
TypeError: Cannot read properties of null (reading '_latLngToNewLayerPoint')
at NewClass._animateZoom
A more simple example of this happening is when I click a marker to see the popup. After seeing the popup, I close the popup and then try to zoom and I get errors on every zoom.
When I first close the popup, I get a warning saying that listener not found which is odd, because I close the popup by simply clicking on the map and I DO have a listener for when I click on the map.
here is the code:
var mapLink = 'Esri';
var wholink = 'i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community';
var satelliteLayer = L.tileLayer(
'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
attribution: '© ' + mapLink + ', ' + wholink,
maxZoom: 18,
});
var osm = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '© OpenStreetMap'
});
var mapboxUrl = 'https://api.mapbox.com/styles/v1/johnmichel/ciobach7h0084b3nf482gfvvr/tiles/{z}/{x}/{y}?access_token=pk.eyJ1Ijoiam9obm1pY2hlbCIsImEiOiJjaW9iOW1vbHUwMGEzdnJseWNranhiMHpxIn0.leVOjMBazNl6v4h9MT7Glw';
var mapboxAttribution = 'Map data © OpenStreetMap contributors, CC-BY-SA, Imagery © Mapbox'
var streets = L.tileLayer(mapboxUrl, { id: 'mapbox/streets-v11', tileSize: 512, zoomOffset: -1, attribution: mapboxAttribution });
var map = L.map("mapContainer", {
center: [lat, lng],
zoom: 10,
layers: [osm, satelliteLayer, streets],
maxZoom: 18,
minZoom: 10,
smoothWheelZoom: true, // enable smooth zoom
smoothSensitivity: 1, // zoom speed. default is 1
smoothZoom: true,
smoothZoomDelay: 1000 // Default to 1000
}).on('click', this.onMapClick);
map.on('moveend', this.onMapMoved)
var baseMaps = {
"OpenStreetMap": osm,
"Satellite": satelliteLayer,
"Mapbox Streets": streets
};
var kayakLaunchLayer = L.layerGroup([]);
var yourPin = L.layerGroup([]);
this.markerLayerGroup = L.layerGroup();
this.singleMarkerLayerGroup = L.layerGroup();
var layerControl = L.control.layers(baseMaps).addTo(map);
map.addLayer(kayakLaunchLayer);
map.addLayer(yourPin);
this.markerLayerGroup = kayakLaunchLayer;
this.singleMarkerLayerGroup = yourPin;
// layerControl.addOverlay(this.markerLayerGroup, "Kayak Launches");
// layerControl.addOverlay(this.singleMarkerLayerGroup, "Your Pin");
map.invalidateSize();
this.map = map;
this.map is a variable defined in data in my Vue component and so is this.singleMarkerLayerGroup and this.markerLayerGroup.
And this is the function where I actually add the markers to the this.markerLayerGroup so that I can see them as an overlay on the map:
var lat = launch.loc.coordinates[1];
var lng = launch.loc.coordinates[0];
var marker = L.marker([lat, lng]).addTo(this.markerLayerGroup).on('click', this.onMarkerClick);
var photoImgwithContent = `<h2>${launch.name}</h2><img src="${launch.images[0]}" height="150px" width="150px"/><h3></h3>${this.capitalizeFirstLetter(launch.waterType)}`;
marker.bindPopup(photoImgwithContent);
Now what is wrong with any of this? I have restructured the code to follow documentation as closely as possible and still stumped.
But strangely, if I turn off zoom animation by applying this map option: zoomAnimation:false, then the error is gone but the map look terrible without zoom animation.

This seems like a similar situation as in Uncaught TypeError: this._map is null (Vue.js 3, Leaflet), since you seem to use Vue.js 3, and the error occurs when zooming the map after some Layers/Popup are removed:
the culprit is the proxying of this.map by Vue, which seems to interfere with Leaflet events (un)binding. It looks like Vue 3 now automatically performs deep proxying
A solution consists in "unwrapping" / un-proxying the map and the Layer Groups (i.e. all Leaflet objects that you store in this Vue component data) whenever you use them, e.g. with Vue3's toRaw utility function:
var marker = L.marker([lat, lng])
.addTo(toRaw(this.markerLayerGroup));
See also the Vue 3 guide to Reduce Reactivity Overhead for Large Immutable Structures.

Related

Two maps on the same page with Leaflet JS, second one not rendering properly

A project I'm working on requires 2 maps on the same page.
I've differeniatiated them using unique varibales and HTML id's, yet the second map never renders properly. It has grey tiles on it. However when I put the second map in the place where the first map is, it renders without any problems.
Map 1 JS
center: [ 0E-16 , 0E-16 ],
zoom: 1.75,
minZoom: 1.75,
}
let memory_map = new L.map('memory_map', memory_mapOptions);
let memory_cartodbAttribution = '© OpenStreetMap contributors, © CARTO';
let memory_layer = new L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png', {attribution: memory_cartodbAttribution}).addTo(memory_map)
memory_map.addLayer(memory_layer);
let memory_popup = new L.popup();
function memory_onMapClick(e) {
memory_popup
.setLatLng(e.latlng)
.setContent("You clicked the map at " + e.latlng.toString())
.openOn(memory_map);
}
memory_map.on('click', memory_onMapClick);
Map 2 JS
let future_mapOptions = {
center: [ 0E-16 , 0E-16 ],
zoom: 1.75,
minZoom: 1.75,
}
let future_map = new L.map('future_map', future_mapOptions);
let future_cartodbAttribution = '© OpenStreetMap contributors, © CARTO';
let future_layer = new L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png', {attribution: future_cartodbAttribution}).addTo(future_map)
future_map.addLayer(future_layer);
let future_popup = L.popup();
function future_onMapClick(f) {
future_popup
.setLatLng(f.latlng)
.setContent("You clicked the map at " + e.latlng.toString())
.openOn(future_map);
}
future_map.on('click', future_onMapClick);
Solutions I've Tried
I copied and pasted the code from the LeafletJS examples, but received the same issue of the map loading incorrectly. This example used a different map and attribution.
Moved the position of the JS code for LeafletJS to be directly above the map in HTML.
Double checked all the avriables are unique (They are).
Swapped the maps around to see if it was an issue with the Map 2 JS. It renderered OK when in Map 1's position on the page
Any help much appreciated!

Leaflet sometimes shows World Maps when it is set to current location

I have setup a map to the current location as shown below.
var mymap = L.map('mapid', {doubleClickZoom: false}).locate({setView: true, maxZoom: 22});
But sometimes the map does not load to current location. It shows a map of the world. For example:
Why is this happening?
This happens if the user blocks the browser from accessing his/her location. Set a default center so that users who block location access do not see the world view. For example:
var mymap = L.map('mapid', {
minZoom: 9,
maxZoom: 18,
zoom: 12,
center: [40.423494,-3.682068],
doubleClickZoom: false,
}).locate({setView: true});
See my Codepen for an example: https://codepen.io/amapolauditiva/pen/XWmdMaw

Binding drawn shapes/markers with specific toggling layer : Leaflet

I am working with the application Beta_Here which uses leaflet plugins, all libraries are local except for few(css related)
Usage of application live
First View:This application get input from user and set the distance
calculation formula accordingly....
Second View : After entering input e.g 9, second view will be loaded
where we can draw shapes....
Introduction
I have setup the script which will load two imageoverlays(layers) and
we can toggle them from top right and we can draw or measure from
bottom left....
Problem
When we draw shapes or put markers on an image, controls work nearly
perfect but when we toggle the layers, there starts the problem....
all shapes go to the background or (it seems they disappeared)
Main Question
How can we bind the drawings and marker to the specific
layer(imageoverlay) if there is a way as we can see the drawing are
not bind with the images but the map container..... (Pardon me if you
feel i am doing something stupid because i have limited knowledge
about layers so i came up with my question here....
If someone has idea about how to solve this problem, please do help or
any kind of reference will be appreciated... Thanks for your time
Working Script
var map = L.map('map', {
minZoom: 1,
maxZoom: 4,
center: [0, 0],
zoom: 0,
crs: L.CRS.Simple
});
// dimensions of the image
var w = 3200,
h = 1900,
mainurl = 'assets/img/isbimg.jpg';
childurl = 'assets/img/fjmap.png';
// calculate the edges of the image, in coordinate space
var southWest = map.unproject([0, h], map.getMaxZoom() - 1);
var northEast = map.unproject([w, 0], map.getMaxZoom() - 1);
var bounds = new L.LatLngBounds(southWest, northEast);
var featureGroup = L.featureGroup().addTo(map);
var drawControl = new L.Control.Draw({
edit: {
featureGroup: featureGroup
},
draw: {
polygon: true,
polyline: true,
rectangle: true,
circle: true,
marker: true
}
}).addTo(map);
map.on('draw:created', showPolygonArea);
map.on('draw:edited', showPolygonAreaEdited);
// add the image overlay,so that it covers the entire map
L.control.layers({
Main: L.imageOverlay(mainurl, bounds),
Child: L.imageOverlay(childurl, bounds)
}, null, { collapsed: false }).addTo(map);
L.control.nanomeasure({ nanometersPerPixel: 10000 }).addTo(map);
// tell leaflet that the map is exactly as big as the image
map.setMaxBounds(bounds);
L.tileLayer({
attribution: 'SmartMinds',
maxZoom: 18
}).addTo(map);
//polygon area customization
function showPolygonAreaEdited(e) {
e.layers.eachLayer(function (layer) {
showPolygonArea({ layer: layer });
});
}
function showPolygonArea(e) {
var userInputCustom = prompt("Please enter image name : choose between a to f");
featureGroup.addLayer(e.layer);
e.layer.bindPopup("<div style='width:200px;height:200px;background-image: url(assets/img/" + userInputCustom + ".png);background-size: 195px 195px;;background-repeat: no-repeat;'></div>");
e.layer.openPopup();
}
});
I would contain those FeatureGroup and ImageOverlay pairs into L.LayerGroup's. Then you can switch between those groups. Then you can keep track of the currently selected group, and add your features to the featurelayer of that group. I can explain it better with code through comments:
Basic map, nothing special:
var map = L.map('map', {
'center': [0, 0],
'zoom': 1,
'layers': [
L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
'attribution': 'Map data © OpenStreetMap contributors'
})
]
});
// Bounds for the map and imageoverlays
var bounds = L.latLngBounds([[40.712216, -74.22655],[40.773941, -74.12544]]);
// Set bounds on the map
map.fitBounds(bounds);
The grouping part:
// New layergroup, note it's not added to the map yet
var layerGroup = new L.LayerGroup(),
imageOverlayUrl = 'https://placeholdit.imgix.net/~text?txtsize=33&txt=Overlay 1&w=294&h=238',
// New imageoverlay added to the layergroup
imageOverlay = new L.ImageOverlay(imageOverlayUrl, bounds).addTo(layerGroup),
// New featuregroup added to the layergroup
featureGroup = new L.FeatureGroup().addTo(layerGroup);
// Second layergroup not added to the map yet
var layerGroup2 = new L.LayerGroup(),
imageOverlayUrl2 = 'https://placeholdit.imgix.net/~text?txtsize=33&txt=Overlay 2&w=294&h=238',
// New imageoverlay added to the second layergroup
imageOverlay2 = new L.imageOverlay(imageOverlayUrl2, bounds).addTo(layerGroup2),
// New featuregroup added to the second layergroup
featureGroup2 = new L.FeatureGroup().addTo(layerGroup2);
Default drawcontrol and layercontrol with both layergroups added as baselayers:
var layerControl = new L.control.layers({
'Group 1': layerGroup,
'Group 2': layerGroup2
}).addTo(map);
var drawControl = new L.Control.Draw().addTo(map);
Here's where the magic happens ;) :
// Variable to hold the selected layergroup's featuregroup.
var currentFeatureGroup;
// Catch the layer change event
map.on('baselayerchange', function (layersControlEvent) {
// Loop over the layers contained in the current group
layersControlEvent.layer.eachLayer(function (layer) {
// If it's the imageoverlay make sure it's in the background
if (layer instanceof L.ImageOverlay) {
layer.bringToBack();
// If not then it's the featuregroup, reference with variable.
} else {
currentFeatureGroup = layer;
}
});
});
// Catch draw created event
map.on('draw:created', function (e) {
// Store created feature into the current featuregroup
currentFeatureGroup.addLayer(e.layer);
});
That's it. Pretty basic just meant as an example but it does what you want it to do. A real implementation would look different, with errorhandling because for instance when you draw and have no baselayer/overlay selected it fail etc. Here's a working example on Plunker to play with: http://plnkr.co/edit/6cGceX?p=preview

Generating leaflet markers with forEach in Meteor.js

I am trying to generate a marker for every geotag in a database using Meteor and leaflet package which I've added through Meteorite.
I've been messing around with the following code and nothing seems to work:
Geoposts = new Meteor.Collection("geoposts");
Geoposts.insert(
{"location":
{"latitude": "40.4417",
"longitude": "-80.000"},
"message": "Hi, I am a message."}
);
if (Meteor.isClient) {
Template.sites.rendered = function() {
// create a map in the "map" div, set the view to a given place and zoom
var map = L.map('map').setView([40.4417, -80.0000], 13);
// add an OpenStreetMap tile layer
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
// add a marker in the given location, attach some popup content to it and open the popup
var myIcon = L.icon({
iconUrl: 'packages/leaflet/images/marker-icon.png',
shadowUrl: 'packages/leaflet/images/marker-shadow.png',
});
var coordForPin = Geoposts.find({location:{latitude:{}}, longitude:{}});
coordForPin.forEach(function(){
L.marker([coordForPin], {icon: myIcon}).addTo(map);
});
}};
if (Meteor.isServer) {
Meteor.startup(function () {
// code to run on server at startup
})};
If anyone can help, it would be much appreciated! Thanks!!
The callback argument to cursor.forEach should take and use the document as an argument. Also, if your Geoposts collection is not static, consider observing your cursor to add/remove/change map markers reactively, rather than using forEach.

How to change the map center in Leaflet.js

The following code initializes a leaflet map. The initialize function centers the map based on user location. How do I change the center of the map to a new position after calling the initialize function?
function initialize() {
map = L.map('map');
L.tileLayer('http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png', {
maxZoom: 18,
attribution: 'Map data © OpenStreetMap contributors, CC-BY-SA, Imagery © CloudMade'
}).addTo(map);
map.locate({setView: true, maxZoom: 8});
}
For example:
map.panTo(new L.LatLng(40.737, -73.923));
You can also use:
map.setView(new L.LatLng(40.737, -73.923), 8);
It just depends on what behavior you want. map.panTo() will pan to the location with zoom/pan animation, while map.setView() immediately set the new view to the desired location/zoom level.
Use map.panTo(); does not do anything if the point is in the current view. Use map.setView() instead.
I had a polyline and I had to center map to a new point in polyline at every second. Check the code :
GOOD: https://jsfiddle.net/nstudor/xcmdwfjk/
mymap.setView(point, 11, { animation: true });
BAD: https://jsfiddle.net/nstudor/Lgahv905/
mymap.panTo(point);
mymap.setZoom(11);
You could also use:
var latLon = L.latLng(40.737, -73.923);
var bounds = latLon.toBounds(500); // 500 = metres
map.panTo(latLon).fitBounds(bounds);
This will set the view level to fit the bounds in the map leaflet.
I was looking for a way to change the bounds but without the animation. This worked for me:
var bounds = L.latLng(40.737, -73.923).toBounds();
map.fitBounds(bounds, {animation: false});
In the case of map-centering problem despite using panTo(),flyTo() or setView() try to adjust the map with map.invalidateSize():
setTimeout(function () {
map.invalidateSize(true);
}, 100);
jQuery sample:
$(document).ready(function () {
mymap.invalidateSize();
});

Categories