Leaflet Clustering with multiple layers (use MarkerCluster.LayerSupport?) - leaflet

Alright, so I've tried and failed a number of times and dug through SE hoping to figure out my problems.
I've been basing a lot of my work off this SE post: link.
I have been unable to make this work, mainly because of two errors, but the first one being an obvious hurdle to overcome first:
Error 1:
Uncaught SyntaxError: Unexpected token <
Error 2:
Uncaught TypeError: L.markerClusterGroup.layerSupport is not a function
So, I would like clustering to work with any layer that is turned on via the L.control.layers() function.
Here is my page, as it sits now: TN Alcohol Map
And the code, sans headers/misc:
// initialize the map
var map = L.map('map').setView([36.17, -86.78], 7);
// load a tile layer/base map
L.tileLayer('http://stamen-tiles-{s}.a.ssl.fastly.net/terrain/{z}/{x}/{y}.png',
{
attribution: 'Map tiles by Stamen Design, CC BY 3.0 — Map data © OpenStreetMap',
maxZoom: 18,
minZoom: 7
}).addTo(map);
//attributes for basemap credit (lower right hand corner annotation)
var streetsAttr = 'Map tiles by Stamen Design, CC BY 3.0 — Map data © OpenStreetMap';
var aerialAttr = 'Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community';
var artsyfartsyAttr = 'Map tiles by Stamen Design, CC BY 3.0 — Map data © OpenStreetMap';
//crete variables for the base map layer switcher
var streets = L.tileLayer('http://stamen-tiles-{s}.a.ssl.fastly.net/terrain/{z}/{x}/{y}.png', {id: 'MapID', attribution: streetsAttr}),
aerial = L.tileLayer('http://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {id: 'MapID', attribution: aerialAttr}),
artsyfartsy = L.tileLayer('http://stamen-tiles-{s}.a.ssl.fastly.net/toner/{z}/{x}/{y}.png', {id: 'MapID', attribution: artsyfartsyAttr});
//create baseMaps variable to store basemap layer switcher
var baseMaps = {
"Streets": streets,
"Aerial": aerial,
"ArtsyFartsy": artsyfartsy
};
// BEER icon & load beer geojson
var beerIcon = L.icon({
iconUrl: 'glass.png',
iconSize: [24, 48]
});
var beerMarker = L.geoJson(false, {
pointToLayer: function(feature, latlng) {
var marker = L.marker(latlng, {
icon: beerIcon
});
//popup shows NAME, ADDRESS, URL and opens the URL in a new window/tab
marker.bindPopup("<strong>" + feature.properties.NAME + "</strong><br/>" + feature.properties.STREETNUM + " " + feature.properties.STREET + ", " + feature.properties.CITY + "<br/>" + "<a target = _blank href=" +
feature.properties.URL + ">" + feature.properties.URLDISPLAY + "</a>");
return marker;
}
}).addTo(map);
$.getJSON("breweries.geojson", function(data) {
beerMarker.addData(data);
});
// WINE icon & load wine geojson
var wineIcon = L.icon({
iconUrl: 'wine.png',
iconSize: [48, 48]
});
var wineMarker = L.geoJson(false, {
pointToLayer: function(feature, latlng) {
var marker = L.marker(latlng, {
icon: wineIcon
});
//popup shows NAME, ADDRESS, URL and opens the URL in a new window/tab
marker.bindPopup("<strong>" + feature.properties.NAME + "</strong><br/>" + feature.properties.STREETNUM + " " + feature.properties.STREET + ", " + feature.properties.CITY + "<br/>" + "<a target = _blank href=" +
feature.properties.URL + ">" + feature.properties.URLDISPLAY + "</a>");
return marker;
}
}).addTo(map);
$.getJSON("wine.geojson", function(data) {
wineMarker.addData(data);
});
//Define overlay maps (non-base layer maps)
var overlayMaps = {
"Breweries": beerMarker,
"Wineries": wineMarker
};
//Creates a Marker Cluster Group
var mcg = L.markerClusterGroup.layerSupport().addTo(map);
//Checking in the 'sub groups'
mcg.checkIn([
beerMarker,
wineMarker
]);
//baseMaps layer switcher
L.control.layers(baseMaps, overlayMaps).addTo(map);

As said by nathansnider in the question comment, the content of your leaflet.markercluster.layersupport-src file is not the JavaScript code of the markerCluster.LayerSupport plugin, but the GitHub HTML page that displays the code of that file, i.e. surrounded by plenty HTML code.
You should simply replace the content of your file by the content of the raw file here: https://raw.githubusercontent.com/ghybs/Leaflet.MarkerCluster.LayerSupport/master/leaflet.markercluster.layersupport-src.js
Demo: http://plnkr.co/edit/Jd8skZ1U0bWxgl2orJV6?p=preview
Side note:
If you just need the Layers Control to work with Leaflet.markercluster, there is another plugin that does just that and which is much simpler: Leaflet.FeatureGroup.SubGroup (230 lines of code v.s. 600 lines for Leaflet.MarkerCluster.LayerSupport as of today).
In your case you would use it this way:
// Create a normal Marker Cluster Group.
var mcg = L.markerClusterGroup().addTo(map);
// Create SubGroups.
var beerMarkerSub = L.featureGroup.subGroup(mcg).addTo(map);
var wineMarkerSub = L.featureGroup.subGroup(mcg).addTo(map);
// For Layers Control.
var overlayMaps = {
"Breweries": beerMarkerSub,
"Wineries": wineMarkerSub
};
// That is it! No need to check-in.
Code specific for your application, since you load GeoJSON data by AJAX:
var beerMarker = L.geoJson(null, beerOptions); // DO NOT add to map.
var wineMarker = L.geoJson(null, wineOptions); // Same story.
$.getJSON("breweries.geojson", function(data) {
beerMarker.addData(data); // GeoJSON conversion.
// Then transfer all features into the corresponding sub-group.
beerMarker.eachLayer(function (layer) {
layer.addTo(beerMarkerSub);
});
});
$.getJSON("wine.geojson", function(data) {
wineMarker.addData(data); // GeoJSON conversion.
// Then transfer all features into the corresponding sub-group.
wineMarker.eachLayer(function (layer) {
layer.addTo(wineMarkerSub);
});
});
Demo: http://plnkr.co/edit/x8vTLjE53TPiLd52BQ1d?p=preview
Disclosure: I am the author of these plugins.

Related

Leaflet: How read pixel dBZ values from the added rain-viewer layer

I am requesting available weather past timestamps from Rain Viewer, below script tag clip:
...
init = function()
{
var apiRequest = new XMLHttpRequest();
apiRequest.open("GET", "https://tilecache.rainviewer.com/api/maps.json", true);
var o_that = this;
apiRequest.onload = function(e) {
...
And then request Leaflet layers with "dBz" data
radarLayers[ts] = new L.TileLayer( 'https://tilecache.rainviewer.com/v2/radar/' + ts + '/256/{z}/{x}/{y}/0/0_0.png' , {
tileSize: 256,
opacity: 0.001,
zIndex: ts
});
I my application I want to check tile pixels for dBz values.
How I can do it JavaScript code?

Leaflet: Layer check box state is reset every time the map moves or zooms

I have the following code which fetches some remote GeoJSON from an API and displays the results on a Leaflet map:
<script>
// Center the map
var map = L.map('map').setView([54.233669, -4.406027], 6);
// Attribution
L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=REMOVED', {
attribution: 'Map © OpenStreetMap',
id: 'mapbox.streets'
}).addTo(map);
// Create an empty layergroup for the data
var LayerUTMGroundHazards = L.layerGroup();
var LayerUTMAirspace = L.layerGroup();
// Style the features
function setStyle(feature) {
return {
fillColor: feature.properties.fillColor,
color: feature.properties.strokeColor,
fillOpacity: feature.properties.fillOpacity,
opacity: feature.properties.strokeOpacity
};
}
// Build Guardian UTM
function getGuardianUTMdata() {
// Clear the current Layer content
LayerUTMGroundHazards.clearLayers();
LayerUTMAirspace.clearLayers();
// Define what we want to include
function FuncGroundHazards(feature) {
if (feature.properties.category === "groundHazard") return true
}
function FuncAirspace(feature) {
if (
(feature.properties.category === "airspace" || feature.properties.category === "airport")
&& feature.properties.detailedCategory !== "uk:frz"
) return true
}
// Build the layers
fetch("https://example.com/json?n=" + map.getBounds().getNorth() + "&e=" + map.getBounds().getEast() + "&s=" + map.getBounds().getSouth() + "&w=" + map.getBounds().getWest(), { headers: { 'Authorization': 'REMOVED', 'X-AA-DeviceId': 'mySite' } })
.then(function (responseGuardianUTM) { return responseGuardianUTM.json() })
.then(function (dataGuardianUTM) {
// Create Layer: Ground Hazards
var featuresUTMGroundHazards = L.geoJson(dataGuardianUTM, {
filter: FuncGroundHazards,
style: setStyle,
pointToLayer: function (feature, latlng) { return L.marker(latlng, { icon: L.icon({ iconUrl: feature.properties.iconUrl, iconSize: [25, 25], }), }) },
onEachFeature: function (feature, layer) { layer.bindPopup(feature.properties.name); },
});
// Add the L.GeoJSON instance to the empty layergroup
LayerUTMGroundHazards.addLayer(featuresUTMGroundHazards).addTo(map);
});
// other layers are here (removed from this example)
}
// Update the Guardian UTM layer if the map moves
map.on('dragend', function () { getGuardianUTMdata(); });
map.on('zoomend', function () { getGuardianUTMdata(); });
// Layer controls
var layerControl = new L.Control.Layers(null, {
'Airspace Restrictions': LayerUTMAirspace,
'Ground Hazards': LayerUTMGroundHazards
// other layers are here (removed from this example)
}).addTo(map);
</script>
The problem is that every time the map is moved or zoomed, all of the Layer checkboxes are reset to Checked again, regardless of how many were checked before the map moved. They do not honour / remember their state when the map moves.
Given my code above, how can I store or preserve the checkbox state for each of the multiple Layers that I have so they are not reset every time the map is moved?
EDIT:
Here is a working fiddle. Remove the checkbox from the 'Ground Hazards', then move or zoom the map, you will see how it puts a tick back in the box again
https://jsfiddle.net/hdwz1b6t/1/
You're (re-)adding LayerUTMGroundHazards every time. This line here...
// Add the L.GeoJSON instance to the empty layergroup
LayerUTMGroundHazards.addLayer(featuresUTMGroundHazards).addTo(map);
...is not only adding featureUTMGroundHazards to layerUTMGroundHazards, it's also (re-)adding layerUTMGroundHazards to the map.
And quoting from https://leafletjs.com/examples/layers-control/ :
The layers control is smart enough to detect what layers we’ve already added and have corresponding checkboxes and radioboxes set.
So when you do LayerUTMGroundHazards.addTo(map);, the checkboxes reset.

Leaflet - only one tile is loaded [duplicate]

I have a problem with Leaflet that actually holds up my whole work. For some reasons I can not explain, the UI of Leaflet is correctly loaded in my Intel XDK app, but there is only one map tile loaded - the same code works in another test app! Now, that I tried everything I could do, I hope that someone here can solve my problem.
For better understanding, here is the code in my leaflet.js (it isn't the leaflet.js, because I'm using the leaflet-src.js as script) and a screenshot of the map window of the app.
function initLeaflet() {
document.getElementById("map").setAttribute("style", "height:" + window.innerHeight + "px; width:" + window.innerWidth + "px;");
var map = L.map('map');
L.tileLayer('https://{s}.tiles.mapbox.com/v3/{id}/{z}/{x}/{y}.png', {
maxZoom: 18,
attribution: 'Map data © OpenStreetMap contributors, ' +
'CC-BY-SA, ' +
'Imagery © Mapbox',
id: 'examples.map-i875mjb7'
}).addTo(map);
map.on('locationfound', onLocationFound);
map.on('locationerror', onLocationError);
map.locate({setView: true, maxZoom: 16});
map.on('click', onMapClick);
}
function onLocationFound(e) {
var radius = e.accuracy / 2;
L.marker(e.latlng).addTo(map)
.bindPopup("Position: " + e.latlng + " Genauigkeit " + radius ).openPopup();
L.circle(e.latlng, radius).addTo(map);
}
function onLocationError(e) {
alert(e.message);
}
function onMapClick(e) {
marker = new L.marker(e.latlng, {id:uni, icon:redIcon, draggable:'true'});
marker.on('dragend', function(event){
var marker = event.target;
var position = marker.getLatLng();
alert(position);
marker.setLatLng([position],{id:uni,draggable:'true'}).bindPopup(position).update();
});
map.addLayer(marker);
}
//var x = document.getElementById("demo");
function getLocation() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(showPosition);
} else {
//x.innerHTML = "Geolocation is not supported by this browser.";
}
}
function showPosition(position) {
//x.innerHTML = "Latitude: " + position.coords.latitude +
//"<br>Longitude: " + position.coords.longitude;
}
http://imgur.com/exOUZuT
I would guess that the size of the map upon initialization is the culprit.
Leaflet needs to know the size of the element it is embedded in when initializing. Leaflet uses that information to know how much tiles to load etc. Furthermore any programmatic changes (or changes that cannot be easily detected by Leaflet) to the size of the map have to be followed by map.invalidateSize(..) link.
I suspect that after you set the size, Leaflet fails to read properly the new size of the #map element. Try invalidating the size afterwards or run initialization asynchronously. I would add:
setTimeout(function () {
map.invalidateSize();
}, 0);
and check if it gets any better.
I used that command to fix my missing tiles problem:
map.getSize();
Look like Leaflet needs to know the size of the element map in advance as Michal said.

How to get all markers on Leaflet

I have a listener that will detect changes of the location of objects on databases. It will pass all the information of the object that is being changed.
I want to get all markers from the current map and find the marker that is affected. Once found, update the location.
But, I am still looking for the best ways to get all markers from the map and then I can update the location.
var map = L.map('map').setView([37.78541,-122.40787], 13);
var markers = new L.FeatureGroup();
var mapLink =
'OpenStreetMap';
L.tileLayer(
'https://{s}.tiles.mapbox.com/v4/examples.map-i87786ca/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoiZ2Vja29iIiwiYSI6IndzVjRGN0kifQ.lToORsjE0lkt-VQ2SXOb-Q', {
attribution: '© ' + mapLink + ' Contributors',
maxZoom: 18,
}).addTo(map);
var marker = createCircleMarker([44.977368, -93.232659]);
marker._id = "69"; // Id of the marker
map.addLayer(marker);
var socket = io();
socket.on('update location', function(obj) {
// Get all markers and find markers with attribute obj.name to
// update the location to [obj.lat,obj.lon]
});
Use eachLayer method on L.map. Like
map.eachLayer(function (layer) {
if (layer.options.name === 'XXXXX') {
layer.setLatLng([newLat,newLon])
}
});
Documentation at http://leafletjs.com/reference-1.2.0.html#map-eachlayer
To add an option without using map.eachLayer; all layers within the map are internally stored in map._layers.
Use
map._layers.forEach(function (layer) {
...
});
to iterate over ALL Layer elements. Not just the ones currently visible.

create web map layers from multiple mbtiles on multiple servers

I'm using this http://blog.carte-libre.fr/index.php?post/2012/02/12/Serve-all-MBTtile-features-with-PHP-script to create a web map with mbtiles hosted on my server.
I want to create selectable layers using several mbtiles (mb1, mb2, mb3) which are stored on different servers (serv1, serv2, serv3).
The script is
wax.tilejson(
'mbtiles-server.php?db=mb1.mbtiles',
function(tilejson) {
var omq = new L.TileLayer(
'http://otile2.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.png', {
maxZoom: 14,
attribution: 'OpenStreetMap - MapQuest',
opacity: 0.4,
});//modify to call mb2 from serv3
var power = new L.TileLayer(
"mbtiles-server.php?db=mb1.mbtiles&z={z}&x={x}&y={y}", {
maxZoom: 14,
attribution: 'OpenStreetMap - CL 2012-02-05',
});
var map = new L.Map('map', {
center: new L.LatLng(46, 0),
zoom: 6,
layers: [omq, power]
});
map.addControl( new L.Control.Layers( { "OpenMapQuest": omq }, { "Power": power }));
wax.leaf.interaction(map, tilejson);
document.getElementById("legend").innerHTML = tilejson.legend;
});
Assuming there is a php script file in each mbtiles folder, how can I modify the script to be able to call mb2 from serv3 so that i have 2 layers from 2 mbtiles hosted on 2 servers?
any advice would be welcomed!
An update...
The issue was trying to load several mbtiles while having respective popup interactions for each layer (each mbtile included interaction, created in TileMill).
I opted to load the mbtiles to the map using:
var base1 = L.tileLayer("http://ms1.mysite.com/folder1/mbtiles-server.php?db=base1layer.mbtiles&z={z}&x={x}&y={y}.png"
var base2 = L.tileLayer("http://ms2.mysite.com/folder2/mbtiles-server.php?db=base2layer.mbtiles&z={z}&x={x}&y={y}.png"
Then added interaction from a separate geojson file.
var onEachFeature_Polygon = function (feature, layer) {
//polygon geojson file contains center points
var centerLat = feature.properties.y;
var centerLon = feature.properties.x;
var centLatLon = new L.LatLng(centerLat, centerLon); //this is used to place the popup in the "mouseover" function
layer.setStyle(tooltip_default_style);
if (feature.properties && feature.properties.Name) {//fields Name, Population, RI10K
var list = "<b>" + feature.properties.Name + "</b>" + "<br>" + "<i>" + "Population: " + feature.properties.Population + "</i>" + "<br>" + "<i>" + "Income 10,000+: " + feature.properties.RI10K + "</i>" + "<br>";//end of tooltip list
layer.bindPopup(list);
}
layer.on("mouseover", function (e) {
layer.setStyle(tooltip_hover_style);
layer.openPopup(centLatLon);
});
layer.on("mouseout", function (e) { layer.setStyle(tooltip_default_style) });
}
//this is the onEachFeature function called when the statsJSON layer is added to the map
var statsinteractive = new L.GeoJSON(statsgeo, { onEachFeature: onEachFeature_Polygon });
statsinteractive.addTo(map);
Would still like to understand or be directed to a clearly outlined method to 'call' interaction from an mbtile file.
In the meantime, I hope this is helpful for someone with a similar issue.