Using minZoom with map.fitBounds without getting error - leaflet

I have a Leaflet map that I am trying to plot multiple points using map.fitbounds() but I would like the set a minZoom so the user cant zoom all the way out past a zoom level of 9. I currently use the below to add pins to a map except sometimes when a map as multiple points where the map would need to fitbounds at a wider zoom it causes the map to fail. When the map fitbounds is set higher than 9 then it works fine but below 9 it doesnt plot.
I was wondering if someone would know how to both safeguard a map from failing with a minZoom and using fitBounds in certain instances and to also stop a user from zooming so far out.
map.options.minZoom = 9;
map.options.maxZoom = 15;
var fg = L.featureGroup().addTo(map);
for ( var i=0; i < markersArray.length; ++i )
{
var linkid = (markersArray[i]['linkid']);
var linkurl = (markersArray[i]['linkurl']);
L.marker( [markersArray[i].lat, markersArray[i].lng], {icon: myIcon} )
.bindPopup( '<div id="mapPinDetails"><h3>' + markersArray[i].name + '</h3>' + markersArray[i].address + '<br /></div>' )
.addTo(fg);
}
map.fitBounds(fg.getBounds());

below 9 it doesnt plot
It does, but since you do not allow your map to zoom out below 9, it remains at zoom level 9 and you may not see your outer markers. But if you pan you can see them. The view center is the same that would have been achieved with a lower zoom:
var map = L.map('map', {
minZoom: 9,
maxZoom: 15,
}).setView([48.86, 2.35], 11);
var fg = L.featureGroup().addTo(map);
L.marker([49, 2.35]).addTo(fg);
L.marker([48.5, 2.35]).addTo(fg);
map.fitBounds(fg.getBounds());
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(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>
If you need further help, you will have to explain what you mean by "it causes the map to fail" and if possible share a reproducible example.

Related

Leaflet lowest zoom level is still too high with L.CRS.Simple

Trying to retrieve part of a district, however for some reason cannot see the whole area, even if zoom level is at 0, where (supposedly) we should see the whole world.
I am using L.CRS.Simple because this uses the EPSG:3763 and cannot see that one on the CRS list. I am retrieving the data in JSON cause when tying with geoJSON, was not able to transform the 3D coordinates data into 2D planes ones.
const queryRegionText = "where=OBJECTID > 0"
const geoJsonURL2 = "https://sig.cm-figfoz.pt/arcgis/rest/services/Internet/MunisigWeb_DadosContexto/MapServer/2/query?f=json&returnGeometry=true&geometryType=esriGeometryPolyline&spatialRel=esriSpatialRelIntersects&outFields=*&outSR=3763&" + queryRegionText
var map = L.map('mapid', {
crs: L.CRS.Simple
}).setView([-58216.458338, 42768.347232], 0);
L.control.scale({ metric: true }).addTo(map);
fetch(geoJsonURL2).then(function (response) {
response.json().then(function (data) {
data.features.forEach(element => {
if (element.geometry.rings) {
element.geometry.rings.forEach(point => {
L.polyline(point, { color: 'red' }).addTo(map);
})
}
});
});
});
var popup = L.popup();
function onMapClick(e) {
popup
.setLatLng(e.latlng)
.setContent("You clicked the map at " + e.latlng.toString())
.openOn(map);
}
map.on('click', onMapClick);
<html>
<head>
<title>Leaflet - testing</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.7.1/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet#1.7.1/dist/leaflet.js"></script>
</head>
<body>
<div id="mapid" style="width: 600px; height: 400px;"></div>
</body>
</html>
TL;DR: When creating the map, set the minimum zoom below zero. This should work:
var map = L.map('mapid', {
crs: L.CRS.Simple, minZoom: -6
}).setView([-57728, 55296], -6);
Explanation
Normally, Leaflet translates from a latitude/longitude coordinate system to screen pixels using an assumption that the world is 256 pixels high at Zoom level 0. At each higher Zoom Level, the number of pixels doubles (explained nicely in the Zoom levels tutorial). With this assumption, the options for the map default to {minZoom: 0, maxZoom: Infinity} (as you are not adding any Layer that sets these values to anything different).
When you use L.CRS.Simple, at Zoom level 0 it maps 1 coordinate unit to 1 screen pixel. Your data looks like it is about 18000 coordinate units tall, so it doesn't fit in your 400 pixel high map. To make it fit, we need each screen pixel to map to about 45 coordinate units. 2^5 is 32, and 2^6 is 64, so we need to zoom out between 5 and 6 times. Luckily, Leaflet accepts negative Zoom Levels, so setting zoom to -6 does the trick. But to make it work properly, you need to set {minZoom: -6}, so the map doesn't get stuck at zoom level 0. There's a good worked example in the Non-geographical Maps tutorial.
Using L.CRS.Simple should work for you, so long as the approximation holds that each latitude unit is the same length as each longitude unit (a square world). Since this isn't generally true in the real world, using the Simple projection will cause some distortion. If the distortion is significant for the features you are interested in, then you will need to look up how to use EPSG:3763 properly, using L.CRS and Proj4Leaflet, as suggested by #IvanSanchez.
So, after some reading on the proj4leaflet, come up with this code. Thanks in advance for the comments and the reply above.
const queryRegionText = "where=OBJECTID > 0"
const geoJsonURL2 = "https://sig.cm-figfoz.pt/arcgis/rest/services/Internet/MunisigWeb_DadosContexto/MapServer/2/query?f=geojson&returnGeometry=true&geometryType=esriGeometryPolyline&spatialRel=esriSpatialRelIntersects&outFields=*&outSR=3763&" + queryRegionText
const map = L.map('map', {
center: [40.14791, -8.87009],
zoom: 13
});
proj4.defs("EPSG:3763", "+proj=tmerc +lat_0=39.66825833333333 +lon_0=-8.133108333333334 +k=1 +x_0=0 +y_0=0 +ellps=GRS80 +units=m +no_defs");
fetch(geoJsonURL2).then(function (response) {
response.json().then(function (data) {
L.Proj.geoJson(data).addTo(map);
});
});
var popup = L.popup();
function onMapClick(e) {
popup
.setLatLng(e.latlng)
.setContent("You clicked the map at " + e.latlng.toString())
.openOn(map);
}
map.on('click', onMapClick);
<head>
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.7.1/dist/leaflet.css" />
<link rel="stylesheet" href="main.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/leaflet.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.7.4/proj4.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/proj4leaflet/1.0.2/proj4leaflet.js"></script>
</head>
<body class="">
<div id="map" style="width: 600px; height: 400px;"></div>
</div>
<script src="main.js"></script>
</body>

Leaflet.JS marker goes on coords on opposite map [duplicate]

This question already has answers here:
How to display Leaflet markers near the 180° meridian?
(4 answers)
Closed 2 years ago.
not exactly sure how to word this, but when i place a leaflet marker that is east of the (i believe) 64 lat mark it goes on a map to the left. i want to make it so all the markers go on the same map.
image on what i'm talking about:
i want to make it so the markers to the left go to the latitude and longitude marks they need to be at where every other marker is. you can see i draw an arrow to show where that is
what i have tried:
i can't find anything so i tried setting worldCopyJump to false. i then tried true and it did not fix this.
thanks!
(oh and if i zoom into the area it should be at it does not appear.)
code:
HTML:
<div id="mapid" style="width: 100%; height: 950px;"></div>
JS
var mymap = L.map('mapid', {worldCopyJump: true}).setView([51.505, -0.09], 3);
L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token=api_key', {
/*maxZoom: 7,*/
attribution: 'Map data © OpenStreetMap contributors, ' +
'Imagery © Mapbox',
id: 'mapbox/streets-v11',
tileSize: 512,
zoomOffset: -1,
//set following lines to false
}).addTo(mymap);
var presentIcon = L.icon({
iconUrl: 'giftmarker.png',
iconSize: [38, 60], // size of the icon
iconAnchor: [12, 57], // point of the icon which will correspond to marker's location
});
L.marker([65.585848, -171.011122], {icon: presentIcon}).addTo(mymap);
L.marker x60 times
You need to add 360 to the Lng of the wrong placed markers.
So the marker should look like: L.marker([65.585848, 188.988878], {icon: presentIcon}).addTo(mymap);
With this code you can chage all existing markers. I take the break at -149 lng to swap the markers to the other side:
map.eachLayer((layer) => {
if (layer instanceof L.Marker) {
if (layer.getLatLng().lng <= -149) {
var latlng = layer.getLatLng();
latlng.lng = latlng.lng + 360;
layer.setLatLng(latlng);
}
}
});

Using several Marker Cluster Groups displays overlapping Clusters

I am using several L.markerClusterGroup({})'s so that I can switch them in a Layers Control.
But the Clusters hide behind each other.
I would like to be able to get the total number of both Clusters.
What am I missing?
The issue is that each Leaflet Marker Cluster Group (i.e. L.markerClusterGroup) will perform its own clustering and render Clusters irrespective of what other Cluster Groups may display. Therefore if you have some individual Markers (or any point Features) that are in different Cluster Groups but close to each other, these Groups will display Clusters also close to each other, which may end up overlapping, especially at low zoom level, exactly like in your screenshot.
If you want your Markers to cluster all together (mixing "oranges with apples"), you should use a single Marker Cluster Group.
Now if I understand correctly, your "difficulty" is that you would like to be able to add and remove the Markers dynamically, i.e. in your case the user can use a Layers Control to switch on/off some Features on map.
In that case, you would probably be interested in Leaflet.FeatureGroup.SubGroup plugin (see demo). Simply create 1 subgroup per "switchable" Features Group and set their parent as your single Marker Cluster Group:
var map = L.map('map', {
maxZoom: 18,
}).setView([48.86, 2.35], 11);
var parentGroup = L.markerClusterGroup().addTo(map);
var overlays = {};
for (var i = 1; i <= 5; i += 1) {
overlays['Group ' + i] = L.featureGroup.subGroup(
parentGroup,
getArrayOfMarkers()
).addTo(map);
}
L.control.layers(null, overlays, {
collapsed: false,
}).addTo(map);
function getArrayOfMarkers() {
var result = [];
for (var i = 0; i < 10; i += 1) {
result.push(L.marker(getRandomLatLng()));
}
return result;
}
function getRandomLatLng() {
return [
48.8 + 0.1 * Math.random(),
2.25 + 0.2 * Math.random()
];
}
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
html,
body,
#map {
height: 100%;
margin: 0;
}
<!-- Leaflet assets -->
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.3.3/dist/leaflet.css" integrity="sha512-Rksm5RenBEKSKFjgI3a41vrjkw4EVPlJ3+OiI65vTjIdo9brlAacEuKOiQ5OFh7cOI1bkDwLqdLw3Zg0cRJAAQ==" crossorigin="" />
<script src="https://unpkg.com/leaflet#1.3.3/dist/leaflet.js" integrity="sha512-tAGcCfR4Sc5ZP5ZoVz0quoZDYX5aCtEm/eu1KhSLj2c9eFrylXZknQYmxUssFaVJKvvc0dJQixhGjG2yXWiV9Q==" crossorigin=""></script>
<!-- Leaflet.markercluster assets -->
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster#1.3.0/dist/MarkerCluster.css">
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster#1.3.0/dist/MarkerCluster.Default.css">
<script src="https://unpkg.com/leaflet.markercluster#1.3.0/dist/leaflet.markercluster-src.js"></script>
<!-- Leaflet.FeatureGroup.SubGroup assets -->
<script src="https://unpkg.com/leaflet.featuregroup.subgroup#1.0.2/dist/leaflet.featuregroup.subgroup.js"></script>
<div id="map"></div>
See also Cluster multiple Layers with markercluster
Note: for more complex use cases, there is also another plugin Leaflet.MarkerCluster.LayerSupport.
See also How to apply leaflet marker cluster using layers
Disclosure: I am the author of these plugins.

Fit map to bounds exactly

Is there a way to fit the map exactly to bounds? (or as close a possible).
For example, in this jsfiddle, even without padding the map leaves a lot of padding above and below the points:
http://jsfiddle.net/BC7q4/444/
map.fitBounds(bounds);
$('#fit1').click(function(){ map.fitBounds(bounds); });
$('#fit2').click(function(){ map.fitBounds(bounds, {padding: [50, 50]}); });
I'm trying to fit a map as precisely as possible to a set of bounds that closely match the map shape without all the extra padding.
Setting a map's ne corner and sw corner would work as well, but I don't think that functionality exists.
You are very probably looking for the map zoomSnap option:
Forces the map's zoom level to always be a multiple of this, particularly right after a fitBounds() or a pinch-zoom. By default, the zoom level snaps to the nearest integer; lower values (e.g. 0.5 or 0.1) allow for greater granularity. A value of 0 means the zoom level will not be snapped after fitBounds or a pinch-zoom.
Because its default value is 1, after your fitBounds the map will floor down the zoom value to the closest available integer value, hence possibly introducing more padding around your bounds.
By specifiying zoomSnap: 0, you ask the map not to snap the zoom value:
var map = L.map('map', {
zoomSnap: 0 // http://leafletjs.com/reference.html#map-zoomsnap
}).setView([51.505, -0.09], 5);
// add an OpenStreetMap tile layer
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
var southWest = new L.LatLng(40.712, -74.227),
northEast = new L.LatLng(40.774, -74.125),
bounds = new L.LatLngBounds(southWest, northEast);
L.marker([40.712, -74.227]).addTo(map);
L.marker([40.774, -74.125]).addTo(map);
map.fitBounds(bounds);
$('#fit1').click(function() {
map.fitBounds(bounds);
});
$('#fit2').click(function() {
map.fitBounds(bounds, {
padding: [50, 50]
});
});
body {
padding: 0;
margin: 0;
}
#map {
height: 300px;
margin-top: 10px;
}
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.2.0/dist/leaflet.css">
<script src="https://unpkg.com/leaflet#1.2.0/dist/leaflet-src.js"></script>
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<button id="fit1">fit without bounds</button>
<button id="fit2">fit with bounds</button>
<div id="map"></div>

custom heatmap leaflet plugin

in index.html I woluld like to add two Heatmaps users can see by checkbox in menu top right corner.
menu show other stuff by code like this
layerControl.addOverlay(geojson, "H2OpenMap");
in this portion of the page (line 383 to line 397)
$.getJSON('api.php', {'wells': '1'}, function(remoteData){
var geojson = L.geoJson(remoteData, {
pointToLayer: function (feature, latlng) {
var icon = chooseIcon(feature['properties']);
var marker = L.marker(latlng, {icon: new h2icon( {iconUrl: icon} )} );
var markerText = buildPopup(feature, true, latlng);
marker.bindPopup(markerText);
return marker;
}
}).addTo(map);
layerControl.addOverlay(geojson, "H2OpenMap");
map.fitBounds(geojson.getBounds(), {'padding': [10,10]});
});
First heat should use data from the same code before, selected by
if(feature['drinking_water'] == 'yes' ) {...
}
Second heat should use data from the same code before, selected by
if(feature['drinking_water'] == 'no' ) {...
}
The goal is to have two heat maps, one for clean water resources the other for not clean water resources, both can be selected by ratio button.
I've find this code looks good but I'm not able to give him data to use to create heatmap.....
//--------------------https://www.patrick-wied.at/static/heatmapjs/plugin-leaflet-layer.html-----------//
$.getJSON('api.php', {'wells': '1'}, function(remoteData){
var geojson = L.geoJson(remoteData, {
pointToLayer: function (feature, latlng) {
var heatData = L.marker(latlng);
console.log(heatData);
//var heatData = L.marker([{lat: new latlng(lat), lng: new latlng(lng)}]);
}})});
/*var testData = {
max: 8,
data: [{lat: 24.6408, lng:46.7728, count: 3},{lat: 50.75, lng:-1.55, count: 1}, ...]
};*/
var cleanWater = heatData;// mettere in un array solo la posizione degli elementi che rispettano la seguente condizione: feature['drinking_water'] == 'yes'
var cfg = {
// radius should be small ONLY if scaleRadius is true (or small radius is intended)
// if scaleRadius is false it will be the constant radius used in pixels
"radius": 2,
"maxOpacity": .8,
// scales the radius based on map zoom
"scaleRadius": true,
// if set to false the heatmap uses the global maximum for colorization
// if activated: uses the data maximum within the current map boundaries
// (there will always be a red spot with useLocalExtremas true)
"useLocalExtrema": true,
// which field name in your data represents the latitude - default "lat"
latField: 'lat',
// which field name in your data represents the longitude - default "lng"
lngField: 'lng',
// which field name in your data represents the data value - default "value"
valueField: 'count'
};
var heatmapLayer = new HeatmapOverlay(cfg);
var map = new L.Map('map-canvas', {
center: new L.LatLng(25.6586, -80.3568),
zoom: 4,
layers: [baseLayer, heatmapLayer]
});
heatmapLayer.setData(cleanWater);
//------------------------//--------------------//--------------------//--------------------//--------------------*/
in the root project it's following file with complete code:
https://github.com/H2OpenMap/map/blob/master/index_heat_test.html
Simplifying your problem I try to suggest you this example ...
http://bl.ocks.org/d3noob/raw/8973028/
This a simple code that implements a Leaflet heat map.
If you see at the source code ...
<!DOCTYPE html>
<html>
<head>
<title>Simple Leaflet Map with Heatmap </title>
<meta charset="utf-8" />
<link
rel="stylesheet"
href="http://cdn.leafletjs.com/leaflet-0.7/leaflet.css"
/>
</head>
<body>
<div id="map" style="width: 600px; height: 400px"></div>
<script
src="http://cdn.leafletjs.com/leaflet-0.7/leaflet.js">
</script>
<script
src="http://leaflet.github.io/Leaflet.heat/dist/leaflet-heat.js">
</script>
<script src="2013-earthquake.js"></script>
<script>
var map = L.map('map').setView([-41.5546,174.146], 10);
mapLink =
'OpenStreetMap';
L.tileLayer(
'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© ' + mapLink + ' Contributors',
maxZoom: 18,
}).addTo(map);
var heat = L.heatLayer(quakePoints,{
radius: 20,
blur: 15,
maxZoom: 17,
}).addTo(map);
</script>
</body>
.... you'll find that the data is simulated with a coordinate array that you can see here ...
http://bl.ocks.org/d3noob/raw/8973028/2013-earthquake.js
I think that you've to convert your geojson data in this format and all will work!
Cesare