leaflet control geojson layers by zoom level - zooming

just looking for a way to control geojson layers by zoomlevel like minZoom , maxZoom for tileLayers, any ideas?
Descrition: geojson points layer, different classes of points, say Nation capitol, Province Capitol, Department, capitol, rest of simple towns.
Goal: at zoom level 4 display only Nation capitol (one only piont)
at zoom level 6 diplay all of 24 province capitols (you will only see a part of them)
at zoom level 8 display 524 departments capitols (you will only see a part of them)
at zoom level 10 display rest of towns, same idea.
thanks in advance.
JC

Check out Map events in the Leaflet Docs.
You can subscribe to the 'zoomend' event as well as 'zoomstart' and a few others
map.on('zoomend', function (e) {
myZoomHandler();
});
In your zoom handler function, loop over your layers and add or remove layers as needed.
function myZoomHandler() {
var currentZoom = map.getZoom();
switch (currentZoom) {
case 4:
//show Capitols
break;
case 6:
//show Provinces
break;
default:
// etc
break;
}
}

Leaflet has a viewereset event that is triggered when the map updates (on load, on zoom).
You can do something like:
map.on('viewreset', function() {
var zoom = this.getZoom();
// show/hide layers based on zoom level
}, map);
One way to show/hide layers is through L.GeoJSON's setStyle() method (use display or visibility properties).

Related

How to check if a marker is in a cluster?

With Leaflet.Markercluster, how is it possible to check if a marker is in a cluster?
Use Leaflet's hasLayer() function
Regular visible markers technically exist as layers in the map. Leaflet also has a function hasLayer() that returns true/false whether a given reference, like a stored marker, currently exists (ie "is visible") in the map as a layer.
For my specific problem, I gave a special visual decoration to a selected map marker (e.g. red border), and this decoration remained even after that marker went in-and-out of a cluster. I needed the selected marker to deselect itself if it entered a cluster.
Below is rough logic that allows me to check whether a given marker reference is visible to the user after a cluster event (that may occur for any reason, like zoom events or non-user driven movements in the map).
Relevant abbreviated code:
I hold my clustering like this...
this.markersClustered = L.markerClusterGroup({ // Lots of stuff here... } })
In my click event for individual markers, I store the marker in a "selectedItem" object (which I use for all kinds of other logic in my app)...
onEachFeature(feature, marker) {
marker.on("click", e => {
// ... (lots of code) ...
// Save instance of the marker; I call it "layer" only
// because I only use this for the hasLayer() check
this.selectedItem.layer = marker
// Here do other stuff too, whatever you need...
//this.selectedItem.target = e.target
//this.selectedItem.latlng = e.latlng
//this.selectedItem.id = e.target.feature.id
//this.selectedItem.icon = layer._icon
// ... (lots of code) ...
})
}
When cluster animations end, using hasLayer(), check whether the selected marker is in a cluster, and do stuff.
Note: the check is done at the END of clustering animation in my use case, because I didn't want the user to see the selected marker lose it's special visual decoration while the clustering animation was happening (it would appear buggy).
this.markersClustered.on('animationend', (e) => {
// Check visibility of selected marker
if(this.map.hasLayer(this.selectedItem.layer)) {
// True: Not in a cluster (ie marker is visible on map)
} else {
// False: In a cluster (ie marker is not visible on map)
}
console.log("Selected marker visible?:" + this.map.hasLayer(this.selectedItem.layer))
})

Get features present in map viewport from geoJSON

I need to add markers on the map for the data that I have.
Out of the thought process, I went through map.data.loadGeoJson('google.json'); with which I will be able to have custom data attached to the map. Now, there is a requirement that I need to show properties of the features present in the map viewport. I cannot loop through all the items and check for .contains(), I have a huge data. Is there default google way to get the properties of the features present in the map viewport?
I like using TurfJS for tasks like these. I've tested it w/ 1M points and it works more quickly than one'd expect.
Pseudocode
load your GeoJSON once and initialize it as a turf feature collection
transform your map viewport's bounds into a GeoJSON polygon
run the pointsWithinPolygon function
pass the resulting feature collection onto the map data layer using addGeoJson
rinse & repeat within a throttled bounds_changed event
Actual Code
/*
You'd load your own geojson like so:
const points = turf.featureCollection(geojsonObject.features)
*/
// random 1M points as a demo
const points = turf.randomPoint(1e6, {
bbox: [-180, -90, 180, 90]
});
const {
west,
south,
east,
north
} = map.getBounds().toJSON();
// extent in minX, minY, maxX, maxY order
const viewport_as_polygon = turf.bboxPolygon([west, south, east, north])
const points_within = turf.pointsWithinPolygon(points, viewport_as_polygon)
Note: Since none of this interacts with the DOM until you call addGeoJSON, it's gonna be faster than for instance generating markers for each feature and then hiding/modifying the transparency of those that are outside of the viewport.

Highcharts not zooming series using option Highcharts.Series.prototype.drawPoints = function() { }

I am creating a graph where I want to show the markers of the series only when hovering. I already tried the solution as shown on this question (Highcharts markers on legend and hover ONLY), and it works perfectly to show the markers when I want to.
However, I have another graph in the same html page that needs to show the markers and when I applied the solution presented on that question on the graph that I want (first graph) and I zoom at the other graph (second graph) the series that are represented by dots does not zoom, they keep all the markers on the graph.
The option
Highcharts.Series.prototype.drawPoints = function() { };
is on line 37 of the jsfiddle https://jsfiddle.net/erifel/sx3dcmny/
You should be able to use marker.enabled: false and series.states.hover.lineWidthPlus: 0 for achieving your chart without reseting drawPoints function. This will give you a possibility to redraw your markers when zooming on you chart.
If you want to show hidden markers in your legend, you can wrap renderItem function in your Legend prototype:
H.wrap(H.Legend.prototype,'renderItem',function(proceed,item){
var enabled = item.options.marker.enabled;
item.options.marker.enabled = true;
proceed.apply(this, Array.prototype.slice.call(arguments, 1));
item.options.marker.enabled = enabled;
});
Here you can see an example how it can work: https://jsfiddle.net/sx3dcmny/12/

Leaflet 0.7.7 Zoombased layer switching separated by theme

We are working on a school project where the aim is to create a map in which based on the amount of zoom the layer switches from one aggregate level to a smaller aggregate level. Additionally, we have several groups of layers based on a theme for which this needs to apply. So you'd click on a theme and a new group of layers that switches based on zoom level become active and when you click another theme another group of layers become active and switch based on zoom level. This means that the themes are exclusionary, ideally you can't have more than one theme active at a time.
We tried to make this work in several ways already but without much success. Using the L.Control.Layers we were unable to group different layers together under one radio button and have them switch based on zoom since the layer control build into leaflet always splits them up into separate ones. Even using L.layerGroup to combine several layer variables or creating several layers into one variable and then adding them to the map using l.control.layer.
We also tried to use L.easyButton (https://github.com/CliffCloud/Leaflet.EasyButton). This allowed us to put the variables under one button and add a zoom based layer switching inside of it. However, the issue here is that we are unable to deactivate the functionality once activated. Which results in several of them being active at one point and overlapping each other.
If possible we would like to know if we should use a different approach or if either the leaflet control function or the use of easyButton could work and how?
This is example code for one of the buttons, which would appear several times but show a different theme:
L.easyButton( '<span class="star">&starf;</span>', function (polygon) {
var ejerlav_polygon = new L.tileLayer.betterWms(
'http://[IP]:[PORT]/geoserver/prlayer/wms', {
layers: 'prlayer:ejerlav',
transparent: true,
styles: 'polygon',
format: 'image/png'});
var municipality_polygon = new L.tileLayer.betterWms(
'http://[IP]:[PORT]/geoserver/prlayer/wms', {
layers: 'prlayer:municipality',
transparent: true,
styles: 'polygon',
format: 'image/png'});
map.on("zoomend", function() {
if (map.getZoom() <= 10 && map.getZoom() >= 2) {
map.addLayer(municipality_polygon);
} else if (map.getZoom() > 10 || map.getZoom() < 2) {
map.removeLayer(municipality_polygon);
}
});
map.on("zoomend", function() {
if (map.getZoom() <= 11 && map.getZoom() >= 11) {
map.addLayer(ejerlav_polygon);
} else if (map.getZoom() > 11 || map.getZoom() < 11) {
map.removeLayer(ejerlav_polygon);
}
});
}).addTo(map);
If my understanding is correct, you would like to give the user the ability to switch between "themes" (some sort of group of layers that switch themselves based on the map current zoom level), possibly using Leaflet Layers Control?
And regarding the switch based on map zoom, you cannot just change the Tile Layer template URL because you use some WMS?
As for the latter functionality (switching layers within a group / theme based on map zoom), a "simple" solution would be to create your own type of layer that will listen to map "zoomend" event and change the Tile Layer WMS accordingly.
L.LayerSwitchByZoom = L.Class.extend({
initialize: function (layersArray) {
var self = this;
this._layersByZoom = layersArray;
this._maxZoom = layersArray.length - 1;
this._switchByZoomReferenced = function () {
self._switchByZoom();
};
},
onAdd: function (map) {
this._map = map;
map.on("zoomend", this._switchByZoomReferenced);
this._switchByZoom();
},
onRemove: function (map) {
map.off("zoomend", this._switchByZoomReferenced);
this._removeCurrentLayer();
this._map = null;
},
addTo: function (map) {
map.addLayer(this);
return this;
},
_switchByZoom: function () {
var map = this._map,
z = Math.min(map.getZoom(), this._maxZoom);
this._removeCurrentLayer();
this._currentLayer = this._layersByZoom[z];
map.addLayer(this._currentLayer);
},
_removeCurrentLayer: function () {
if (this._currentLayer) {
map.removeLayer(this._currentLayer);
}
}
});
You would then instantiate that layer "theme" / group by specifying an array of layers (your Tile Layers WMS), where the array index corresponds to the zoom level at which that Tile Layer should appear.
var myLayerSwitchByZoomA = new L.LayerSwitchByZoom([
osmMapnik, // zoom 0, osmMapnik is a Tile Layer or any other layer
osmDE, // zoom 1
osmFR, // zoom 2
osmHOT // zoom 3, etc.
]);
Once this new layer type is set, you can use it in the Layers Control like any other type of Layer / Tile Layer, etc.
L.control.layers({
"OpenStreetMap": myLayerSwitchByZoomA,
"ThunderForest": myLayerSwitchByZoomB
}).addTo(map);
Demo: http://jsfiddle.net/ve2huzxw/85/
Note that you could further improve the implementation of L.LayerSwitchByZoom to avoid flickering when changing the layer after zoom end, etc.

HTML5 Drag and Drop - Map

I have to make a map of Europe with its countries
and then I need a few pics of products from those countries
After that I have to match the pic with the country with drag and drop
if the product is dropped on the correct country it should send me to another page (with more info about the product)
if it's wrong it should display a message
anyone have an idea? I checked for some basic drag and drop stuff but since I'm new to html5 etc and webdesign in general it's really hard to make this from scratch
thanks!
EDIT: also only use HTML, CSS, JS
This can be achieved with the MapQuest JavaScript API. What I would start with is by adding polygon overlays to the map for each country, the colour can be sett to completely transparent by setting the opacity for the overlay to 0.0. From each overlay add a mouseup event listener to each overlay, this event listener can then be used to determine what it was you were dragging in the first place.
For the drag start functionality you can either do this yourself or you could use something like the jQuery UI draggable support, you could then use the dragstop event from the draggable API in conjunction with mouseup on the overlay to perform your logic.
Check out the basic map to get a map going.
Some code to start with
var countryCode;
// Adds an overlay and wires an event for mouseup.
function addMapOverlay(points, cc) {
var poly = new MQA.PolygonOverlay();
poly.setShapePoints(points);
poly.color = "#ffffff";
poly.colorAlpha=0.0;
poly.fillColor = "#ffffff";
poly.fillColorAlpha=0.0;
poly.addListener(rectangle, 'mouseup', function(evt) {
if (evt.eventName === "mouseup") {
// Here you have the event firing for the mouse-up on the overlay.
countryCode = cc;
}
});
}
For the drag-start.
$("#some-country-item").draggable({
start: function(event, ui) {
countryCode = null;
},
stop: function(event, ui) {
if (countryCode === "what you expected") {
// Released on correct country.
} else {
// Did not release on correct country.
}
}
});
You may need to test the event handling to ensure that the correct events are fired in the right order, or use the mouseover event on the overlay object.
The code samples are theoretical and should help you find the right direction to go.