How to change the map center in Leaflet.js - javascript

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();
});

Related

Leaflet error when zooming after closing popup

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.

Leaflet event: how to propagate to overlapping layers

Let's say I have some overlapping layers and each layer has a click event. When I click on the map, I'd like to know which layers are clicked on, though the click event stops after the first layer and does not propagate to its underlying layers. How can I achieve this?
Here's a sample fiddle and its code: https://jsfiddle.net/r0r0xLoc/
<div id="mapid" style="width: 600px; height: 400px;"></div>
<script>
var mymap = L.map('mapid').setView([51.505, -0.09], 13);
L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw', {
maxZoom: 18,
attribution: 'Map data © OpenStreetMap contributors, ' +
'CC-BY-SA, ' +
'Imagery © Mapbox',
id: 'mapbox.streets'
}).addTo(mymap);
L.polygon([
[51.509, -0.08],
[51.503, -0.06],
[51.51, -0.047]
]).addTo(mymap).on('click', function() {
console.log('clicked on 1st polygon')
});
L.polygon([
[51.609, -0.1],
[51.503, -0.06],
[51.51, -0.047]
]).addTo(mymap).on('click', function() {
console.log('clicked on 2nd polygon')
});
</script>
If you click on each polygon, you see its related message. If you click on the overlapping part, you only see the message for the second polygon.
You have to listen directly to the map "click" event and to "manually" determine which layers contain the clicked position.
You can use leaflet-pip plugin (point in polygon) for example for this determination:
map.on("click", function (event) {
var clickedLayers = leafletPip.pointInLayer(event.latlng, geoJSONlayerGroup);
// Do something with clickedLayers
});
Demo: https://jsfiddle.net/ve2huzxw/526/ (listening to "mousemove" instead of "click")
There is a leaflet plugin for propagating events to the underlying layers: https://github.com/danwild/leaflet-event-forwarder
You can use it in your javascript to enable event-forwarding, e.g.:
const myEventForwarder = new L.eventForwarder({
map: map,
events: {click: true, mousemove: false}
});
The problems is the order in which the layers (geometries) are added. In my case I had an array of geometries and what I did was just sort the geometries array using its bounds L.LatLngBounds#contains, so if a geometry contains another it should be added later.
var geometries = [layerOne, layerTwo, ...];
geometries
.sort((a, b) => {
// in my case a separate function is required because the geometry could be a Circle, Rectangle, Polygon or Marker
// and the methods to get the corresponding bounds are different.
var boundsA = this.getBoundsFromGeometry(a);
var boundsB = this.getBoundsFromGeometry(b);
// if the second geometry contains the first, the order must be change so the layers don't overlap
return boundsB.contains(boundsA) ? 1 : -1;
})
.forEach(l => this.map.addLayer(l));
For me the solution was to use the option interactive: false when creating the upper layer:
var options = {
style: feature => this.__determineStyle(feature),
interactive: false,
};
var overlay = L.geoJson(geoJsonData, options);
overlay.addTo(this.map);
https://leafletjs.com/reference-1.0.3.html#interactive-layer
You can listen on the click event on the map. Then, getting the coordinates (lat, lng) of the clicked point, you can filter your features by checking which of them contain it.
For this, you can use booleanPointInPolygon function of turf.js
The following (sample code and fiddle) use polygons as an example.
map.on('click', e => {
const { lat, lng } = e.latlng;
const point = turf.point([lng, lat]);
/* polygons is an array where all your polygon layers are stored */
polygons.forEach((p, i) => {
const polygon= p.toGeoJSON();
if (turf.booleanPointInPolygon(point, polygon)) {
/* do whatever you want with your clicked polygon */
}
});
});
Check fiddle: https://jsfiddle.net/yfg7mdkx/

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

Preserving Google map zoom to kml layer on checkbox click

I'm attempting to add multiple kml layers to a map that can be turned on and off with check boxes. I got that part working (yay!). When I click on a layer to turn it on, it zooms in (this is fine), but when I unclick to turn the layer off, it zooms back out to my map extent. How do I get it to preserve the zoom of the last loaded layer? Code below.
<script>
var map;
var watershedLayer = new google.maps.KmlLayer ({
url: 'http://mvihes.bc.ca/mapping/watersheds.kmz'
});
var ere1949Layer = new google.maps.KmlLayer ({
url: 'http://mvihes.bc.ca/mapping/ere1949.kmz'
});
function initialize() {
var parksville= new google.maps.LatLng(49.316786, -124.308768);
var mapOptions = {
zoom: 9,
center: parksville
};
map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
check();
}
function check()
{
if(document.getElementById('watersheds').checked)
{watershedLayer.setMap(map);}
else
{watershedLayer.setMap(null);}
if(document.getElementById('ere1949').checked)
{ere1949Layer.setMap(map);}
else
{ere1949Layer.setMap(null);}
}
google.maps.event.addDomListener(window, 'load', initialize);
</script>
I tried using the preserveViewport function but it just stopped the layer from zooming in, which is not what I wanted. I'm new to javaScript so possibly I'm missing something obvious...any help would be appreciated!
jsfiddle
Set the map-property of a selected layer only when it's not set yet:
function check()
{
if(document.getElementById('watersheds').checked)
{if(!watershedLayer.getMap())watershedLayer.setMap(map);}
else
{watershedLayer.setMap(null);}
if(document.getElementById('ere1949').checked)
{if(!ere1949Layer.getMap())ere1949Layer.setMap(map);}
else
{ere1949Layer.setMap(null);}
}
http://jsfiddle.net/jhagmq7L/16/
You can also attach max and min zoom levels to the map, something to consider the over all map zoom when loading kml layers.
// sets the min and max zoom levels of the map
var opt = { minZoom: 6, maxZoom: 18 };
map.setOptions(opt);

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.

Categories