Leaflet Not Adding Tile Layer to Bottom - javascript

I am helping my students with a map of some data related to Hurricane Sandy. They have four tile layers (MapBox Street, MapBox Satellite, NOAA Satellite, and NOAA Inundation) and several vector layers.
When it first loads, the map shows the MapBox Streets tiles with some vector data on top, with the ability to switch to the MapBox Satellite layer as the base map. Users can then add the inundation layer on top of the base map (the inundation layer is complex enough that it needs tiles instead of rendered polygons).
The problem occurs when you switch base map layers after the inundation layer has been added to the map. My students use the L.map.addLayer(data, insertAtTheBottom) function to add the new base map layer to the bottom (after removing the old one), but for some reason the layer is only added below the vector layers and not below the inundation tile layer. When the base layers are switching out you can clearly see that the inundation tile layer is still there, it just won't go on top of the new base layer.
Does anyone know why this is the case? The documentation states that this parameter ensures that the new layer will be added below all other layers.
Live example of the page: http://personal.psu.edu/rsm5068/src/map.html
Relevant code segment
<body>
<div id="map"></div>
<div id="controls">
<input type="checkbox" id="toggleSatellite" /><label for="toggleSatellite">Toggle Satellite</label>
</div>
<script type="text/javascript">
var map = null,
layerStreets,
layerSatelliteNow,
layerSatelliteThen,
layer1MSurge,
layer3MSurge,
lgrpBase,
lgrpSurge;
map = L.map("map", {
'center' : L.latLng(40.7127, -74.0059),
'zoom' : 10,
'minZoom' : 9,
'maxZoom' : 16
})
// ---- Tile Layers ----
layerStreets = L.mapbox.tileLayer("user.id");
layerSatelliteNow = L.mapbox.tileLayer("user.id");
layer1MSurge = L.mapbox.tileLayer("user.id");
layer3MSurge = L.mapbox.tileLayer("user.id");
lgrpSurge = L.layerGroup();
lgrpSurge
.addLayer(layer3MSurge)
.addLayer(layer1MSurge);
// ---- Adding Data to Map ----
map
.setMaxBounds(L.latLngBounds(L.latLng(40.4600,-74.33),L.latLng(40.9400,-73.667)))
.addLayer(layerStreets)
.addLayer(lgrpSurge)
.addLayer(fgrpEvacCenters);
// ---- Interactivity ----
$("#toggleSatellite").change(function () {
if ($("input[id='toggleSatellite']:checked").length > 0) {
map
.removeLayer(layerStreets)
.addLayer(layerSatelliteNow, true);
} else {
map
.removeLayer(layerSatelliteNow)
.addLayer(layerStreets, true);
}
})
</script>
</body>

Your Inundation layer should be added to the map as an overlay and not as a basemap (tiled layers can be overlays as well as basemaps).
First you'll want to distinguish between your basemap layers and your overlays.
Your basemap layers (streets, satellite, etc) are those layers that are mutually exclusive and should always be on the bottom.
Your overlay layers are not mutually exclusive and will always sit on top of the basemap layers.
The following example is from the Leaflet LayersControl docs page
var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/API-key/{styleId}/256/{z}/{x}/{y}.png',
cloudmadeAttribution = 'Map data © 2011 OpenStreetMap contributors, Imagery © 2011 CloudMade';
var minimal = L.tileLayer(cloudmadeUrl, {styleId: 22677, attribution: cloudmadeAttribution}),
midnight = L.tileLayer(cloudmadeUrl, {styleId: 999, attribution: cloudmadeAttribution}),
motorways = L.tileLayer(cloudmadeUrl, {styleId: 46561, attribution: cloudmadeAttribution});
var map = L.map('map', {
center: new L.LatLng(39.73, -104.99),
zoom: 10,
layers: [minimal, motorways, cities]
});
var baseMaps = {
"Minimal": minimal,
"Night View": midnight
};
var overlayMaps = {
"Motorways": motorways,
"Cities": cities
};
L.control.layers(baseMaps, overlayMaps).addTo(map);
At this point, the basemaps (Minimal and Night View) will always be underneath the overlays (Motorways and Cities).
Make sure your Inundation layer is added to the map as an overlay and not a basemap.

This I think is a bug. I have tried it too but it is not working correctly. Please use bringToBack() (or bringToFront() if you want it in front) to force layer to correct position:
So:
map.addLayer(layerSatelliteNow);
layerSatelliteNow.bringToBack();

Related

Leaflet markers aggregating when dezooming the map

I am new to Leaflet and I am trying to show a map with markers.
The problem I have is that the markers disappear when I zoom out, and are replaced with a number:
I used CircleMarkers to be able to set the color of each marker, and so that the markers keep their size no matter the zoom level. I add the markers using markercluster because I want to be able to delete them all easily.
<script src="https://unpkg.com/leaflet.markercluster#1.3.0/dist/leaflet.markercluster.js"></script>
function addMarkers(data){
// setup a marker group
markerList = L.markerClusterGroup();
for (var i = 0; i < data.length; i++){
var circleColor = getcolor(data[i].roundScore);
var circle = L.circleMarker([data[i].solutionLat, data[i].solutionLng], {
color: circleColor,
fillColor: circleColor,
fillOpacity: 0.5,
radius: 5
});
circle.bindPopup("Score : " + data[i].roundScore + "\n Address :" + data[i].address);
markerList.addLayer(circle);
}
window.mapMark.addLayer(markerList);
}
function deleteMarkers(){
if(markerList) {
markerList.clearLayers();
}
}
I have no idea how to change this behavior, and whether it is linked to the markers themselves or to the tile I use, which is openstreetmap:
const attribution =
'© OpenStreetMap contributors';
const tileUrl = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
const tiles = L.tileLayer(tileUrl, { attribution });
tiles.addTo(mapMark);
}
Any help or hint would be greatly appreciated.
Thank you very much :)
The disappearance of your Circle Markers and replacement by numbers is the expected effect of Leaflet.markercluster. should you have included the plugin CSS files as well, these numbers would appear in green/yellow/orange bubbles.
To avoid this clustering, but still be able to remove all your Markers at once, simply use a basic Layer Group instead of a MarkerClusterGroup:
https://leafletjs.com/reference-1.7.1.html#layergroup
Used to group several layers and handle them as one.
markerList = L.layerGroup();
// you can also clear layers of a basic Layer Group
markerList.clearLayers();

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!

Hiding markers using LeafletSlider?

I am showing a temporal range of refugee camps on my map by using the LeafletSlider plugin. The camps appear on the map based on an attribute in my GEOJSON object called DATE_START. As you can see in my JSFIDDLE, the slider works good.
As I am scrubbing the timeline , I want to remove the markers that have a DATE_CLOSED property depending on the date of the current timeline scrub and the date of the DATE_CLOSED property.
It looks like this timeslider plugin only shows markers. Does anyone know how to hide the markers after it date has closed?
Sample data:
var camps = {"type":"FeatureCollection","features":[{"type":"Feature","properties":{"STATUS":"UNOCCUPIED","DATE_START":"2015-06-23","DATE_CLOSED":"2016-01-23"},"geometry":{"type":"Point","coordinates":[64.6875,34.97600151317591]}},{"type":"Feature","properties":{"STATUS":"OCCUPIED","DATE_START":"2014-01-21","DATE_CLOSED":"2015-05-25"},"geometry":{"type":"Point","coordinates":[65.335693359375,36.26199220445664]}},{"type":"Feature","properties":{"STATUS":"UNOCCUPIED","DATE_START":"2015-09-13","DATE_CLOSED":""},"geometry":{"type":"Point","coordinates":[67.587890625,35.969115075774845]}}]};
Code:
var map = L.map('map', {
center: [33.67406853374198, 66.9287109375],
zoom: 7
}).addLayer(new L.TileLayer("http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"));
//Create a marker layer (in the example done via a GeoJSON FeatureCollection)
var testlayer = L.geoJson(camps, {
onEachFeature: function(feature, layer) {
layer.bindPopup(feature.properties.DATE_START);
}
});
var sliderControl = L.control.sliderControl({
position: "topright",
layer: testlayer,
timeAttribute: 'DATE_START'
});
//Make sure to add the slider to the map ;-)
map.addControl(sliderControl);
sliderControl.options.markers.sort(function(a, b) {
return (a.feature.properties.DATE_START > b.feature.properties.DATE_START);
});
//And initialize the slider
sliderControl.startSlider();
$('#slider-timestamp').html(options.markers[ui.value].feature.properties.DATE_START.substr(0, 10));
I hope this counts as an answer, but I discovered there was an alternative timeline plugin that does just what I needed: https://github.com/skeate/Leaflet.timeline

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

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