Zooming In and Showing/Hiding Labels in Leaflet - leaflet

I have run into an issue that I can't find a solution anywhere. In my map, because of the number of features, I wait for the map to be zoomed in to a certain extent prior to showing the labels, I found a solution that was suggested as working, but in an older version.
Below is the code sample. The two console.logs do appear when zoomed in to and past the specified level and zoomed out and past the specified level. But the labels just don't appear at all.
mymap.on('zoomend', function() {
var zoom = mymap.getZoom();
if( mymap.hasLayer(lots) ) {
lots.eachLayer( function (layer){
//console.log(layer);
if ( zoom >= 21 && (!layer.getTooltip()) ) {
layer.bindTooltip(layer.feature.properties.lot_number, { sticky: true ,permanent: true, interactive: false , direction: 'center',className: 'countryLabel'});
console.log('zoomedin');
} else if ( zoom < 21 && (layer.getTooltip()) ) {
//console.log('remove tooltip');
layer.unbindTooltip();
console.log('zoomedout');
}
});
}
});
This is how my lot feature data structure is defined prior to and shows up correctly on the map.
{
"properties": {"lot_number": "{{$l->lot_number}}", "lot_id": "{{$l->id}}", "status_color": "{{$l->status->color}}","block_number": "{{$l->block->id}}","section_number": "{{$l->section->section_number}}"},
"type": "{{$l->coordinates['type']}}",
"coordinates": [[{{json_encode($l->coordinates['coordinates'][0][0])}}]]
},
If anyone has any suggestions, I would greatly appreciate them. Thank you!
I have discovered it isn't a zoom level issue because the divs do appear once past the zoom level required. It's somohow in the formatting of the data I want to put in the Tooltip.
In another portion of code I retrieve the properties like such:
var lots = L.geoJSON(myLines, {
onEachFeature: function(feature, layer) {
layer.bindPopup("<b>Lot Record #"+feature.properties.lot_number+"</b><br/>Block "+feature.properties.block_number+"<br>Section "+feature.properties.section_number+"<br><a href='lots/view/"+feature.properties.lot_id+"'>View Details</a>");
layer.setStyle({
fillColor: feature.properties.status_color,
fillOpacity: 0.8,
weight: 0.5
});
}
}).addTo(mymap);
But the above is through the feature.properties... whereas the bindTooltip method I am trying call that is having the issue uses layer.feature. but I am unsure regarding the rest of the statement to get to the properties correctly.
I found my problem to be that by going through the "layer" before the "feature", the properties is accessed through a different method. The correct format should be layer.feature.geometry.properties.lot_number.

Your code is working for me: https://jsfiddle.net/falkedesign/5x2r37pL/
I test it with zoom lvl 10:
if ( zoom >= 10 && (!layer.getTooltip()) ) {
Maybe is the zoom with 21 to high for your map

I found my problem to be that by going through the "layer" before the "feature", the properties is accessed through a different method. The correct format should be layer.feature.geometry.properties.lot_number. This was accomplished by logging the features and tracing their data structures returned in the objects.

Related

how to hide the leaflet tooltip (visibility already depending on zoom level) when activating a layer?

I am interested in showing the tooltip only for a specific zoom range (>=12) at any time. With the help of this other article I could manage to almost get there but I still have the problem that the tooltip come to appear when I activate the layer in my OverlayMaps-menu, disregarding the current zoom level.
In my leaflet map I have implemented a OverlayMaps-menu, in which almost all layers are deactivated from the start. For one of these deactivated layers, my aim is to show the tooltips for all features only from a zoom level of 12 or bigger. The code is as follows:
Declaration of the variable from a geoJSON object:
var vibro_offshore = L.geoJson (vbc_offshore, {
pointToLayer: function (feature, latlng) {
return new L.Circle (latlng, {
color:'#a13c02',
})
},
onEachFeature: enCadaVBC
});
In the onEachFeature function, beside some other code regarding popup, I also have defined the tooltips:
function enCadaVBC (feature, layer) {
//declaramos un tooltip
var tooltip = L.tooltip({
content: feature.properties.Location,
permanent: true,
direction:'right',
className:'tooltip'
});
//introducimos un tooltip
layer.bindTooltip(tooltip);
Finally, I have managed to govern the visibility of the tooltips depending on the zoom level with the code suggested in the article referred above:
var lastZoom;
map.on('zoomend', function() {
var zoom = map.getZoom();
console.log(zoom);
if (zoom < 12 && (!lastZoom || lastZoom >= 12)) {
map.eachLayer(function(l) {
if (l.getTooltip) {
var toolTip = l.getTooltip();
if (toolTip) {
this.map.closeTooltip(toolTip);
}
}
});
} else if (zoom >= 12 && (!lastZoom || lastZoom < 12)) {
map.eachLayer(function(l) {
if (l.getTooltip) {
var toolTip = l.getTooltip();
if (toolTip) {
console.log(toolTip);
this.map.addLayer(toolTip);
}
}
});
}
lastZoom = zoom;
})
All this works pretty good, but when I activate the layer via mouseclick on the OverlayMaps-menu, lets say when the zoom level is 9, all the tooltips suddenly appear. I want to avoid this.
I have tried to add a closeTooltip() command right after the bindTooltip-command, but this hasnt have any effect. A consol.log for Is Tooltipopen leads to an undefined...(?)
I also tried to relocate the layer.bindTooltip command into the if-loop, but here I do get some other error related to Mercator projection and latlng, which I do not really understand...

How to set the zIndex layer order for geoJson layers?

I would like to have certain layers to be always on top of others, no matter in which order they are added to the map.
I am aware of bringToFront(), but it does not meet my requirements. I would like to set the zIndex dynamically based on properties.
Leaflet has the method setZIndex(), but this apparently does not work for geoJson layers:
https://jsfiddle.net/jw2srhwn/
Any ideas?
Cannot be done for vector geometries.
zIndex is a property of HTMLElements, and vector geometries (lines and polygons) are rendered as SVG elements, or programatically as <canvas> draw calls. Those two methods have no concept of zIndex, so the only thing that works is pushing elements to the top (or bottom) of the SVG group or <canvas> draw sequence.
Also, remind that L.GeoJSON is just a specific type of L.LayerGroup, in your case containing instances of L.Polygon. Furthermore, if you read Leaflet's documentation about the setZIndex() method on L.LayerGroup:
Calls setZIndex on every layer contained in this group, passing the z-index.
So, do L.Polygons have a setZIndex() method? No. So calling that in their containing group does nothing. It will have an effect on any L.GridLayers contained in that group, though.
Coming back to your problem:
I would like to have certain layers to be always on top of others, no matter in which order they are added to the map.
Looks like the thing you're looking for is map panes. Do read the map panes tutorial.
This is one of the reason for the implementation of user defined "panes" in Leaflet 1.0 (compared to versions 0.x).
Create panes: var myPane = map.createPane("myPaneName")
If necessary, set the class / z-index of the pane element: myPane.style.zIndex = 450 (refer to z-index values of built-in panes)
When creating your layers, specify their target pane option: L.rectangle(corners, { pane: "myPaneName" })
When building through the L.geoJSON factory, you can loop through your features with the onEachFeature option to clone your layers with specified target pane.
Demo: https://jsfiddle.net/3v7hd2vx/90/
For peoples who are searching about Z-Index
All path layers (so all except for markers) have no z-index because svg layers have a fix order. The first element is painted first. So the last element is painted on top.
#IvanSanchez described good why zIndex not working.
You can control the order with layer.bringToBack() or layer.bringToFront().
With that code you have more options to control the order of the layers.
L.Path.include({
getZIndex: function() {
var node = this._path;
var index = 0;
while ( (node = node.previousElementSibling) ) {
index++;
}
return index;
},
setZIndex: function(idx) {
var obj1 = this._path;
var parent = obj1.parentNode;
if(parent.childNodes.length < idx){
idx = parent.childNodes.length-1;
}
var obj2 = parent.childNodes[idx];
if(obj2 === undefined || obj2 === null){
return;
}
var next2 = obj2.nextSibling;
if (next2 === obj1) {
parent.insertBefore(obj1, obj2);
} else {
parent.insertBefore(obj2, obj1);
if (next2) {
parent.insertBefore(obj1, next2);
} else {
parent.appendChild(obj1);
}
}
},
oneUp: function(){
this.setZIndex(this.getZIndex()+1)
},
oneDown: function(){
this.setZIndex(this.getZIndex()-1)
}
});
Then you can call
polygon.oneUp()
polygon.oneDown()
polygon.setZIndex(2)
polygon.getZIndex()
And now layergroup.setZIndex(2) are working

Updating layers in Leaflet / Mapbox

I'm trying to make a mapping visualization in realtime, where I keep getting new points via websockets. The initial plotting these markers on the map seems simple, but I'm not sure what's the right way of updating a layer on Mapbox.
As of now, whenever I get a new point, I remove the old layer, create a new one and then add it on the map. The problem with this approach is that it is slow and for high number of points (>5000) it starts lagging.
// remove layer
if (this.pointsLayer != null) {
map.removeLayer(this.pointsLayer);
}
// build geoJSON
var geoJSON = { "type": "FeatureCollection", "features": [] };
geoJSON["features"] = tweets.map(function(tweet) {
return this.getGeoPoint(tweet);
}.bind(this));
// add geoJSON to layer
this.pointsLayer = L.mapbox.featureLayer(geoJSON, {
pointToLayer: function(feature, latlon) {
return L.circleMarker(latlon, {
fillColor: '#AA5042',
fillOpacity: 0.7,
radius: 3,
stroke: false
});
}
}).addTo(map);
Is there a better way?
You can create an empty GeoJSON layer by passing it a false instead of real data:
//create empty layer
this.pointsLayer = L.mapbox.featureLayer(false, {
pointToLayer: function(feature, latlon) {
return L.circleMarker(latlon, {
fillColor: '#AA5042',
fillOpacity: 0.7,
radius: 3,
stroke: false
});
}
}).addTo(map);
then use .addData to update it as new tweets come in. Something like:
// build geoJSON
var geoJSON = { "type": "FeatureCollection", "features": [] };
geoJSON["features"] = /**whatever function you use to build a single tweet's geoJSON**/
// add geoJSON to layer
this.pointsLayer.addData(geoJSON);
For a single tweet, I guess you could just create a Feature instead of a FeatureCollection, though I don't know whether that extra layer of abstraction would make any difference in terms of performance.
EDIT: Here is an example fiddle showing the .addData method at work:
http://jsfiddle.net/nathansnider/4mwrwo0t/
It does slow down noticeably if you add 10,000 points, and for 15,000 points, it's really sluggish, but I suspect that has less to do with how the points are added that the demands of rendering so many circleMarkers.
If you aren't already, you may want to try using the new Leaflet 1.0 beta, which redraws vector layers faster and is generally much more responsive with large datasets. Compare this 15,000-point example using Leaflet 0.7.5 to the same code using Leaflet 1.0.0b2. Not everything is fixed (popups take a long time to open in both), but the difference in lag time when trying to drag the map is pretty dramatic.
There's no reason to go through the intermediate step of construction a GeoJSON object just so you can add it to the map. Depending on your exact needs, you can do something like this:
tweets.forEach(function(t) {
L.marker(this.getGeoPoint(t)).addTo(map);
}, this);
You should manage the tweets object so it only contains points that are not already visible on the map, though. Deleting all the old markers, just so you can add them again, is of course going to be very slow.
I would take a look at Leaflet Realtime:
Put realtime data on a Leaflet map: live tracking GPS units, sensor data or just about anything.
https://github.com/perliedman/leaflet-realtime

Layer order changing when turning layer on/off

I have two geoJson layers being loaded - both layers are the same data for testing purposes, but being drawn from two different json files. When I turn the layers on and off in the layer controller, the draw order of the layers change.
Any ideas why this is happening?
I have put my code into a JSFiddle: http://jsfiddle.net/lprashad/ph5y9/10/ and the JS is below:
//styling for watersheds_copy
var Orange = {
"color": "#ff7800",
"weight": 5,
"opacity": 0.65
};
var Water_Orange = L.geoJson(watersheds_copy, {
style: Orange
});
Water_Orange.addData(watersheds_copy);
//these are blue
var Water_blue = L.geoJson(watersheds, {});
Water_blue.addData(watersheds);
//This sets the inital order - last in layer list being on top. Except minimal - tile layer is always on bottom
var map = L.map('map', {
center: [41.609, -74.028],
zoom: 8,
layers: [minimal, Water_Orange, Water_blue]
});
var baseLayers = {
"Minimal": minimal,
"Night View": midnight
};
//This controls the order in the layer switcher. This does not change draw order
var overlays = {
"Water_Orange": Water_Orange,
"Water_blue": Water_blue
};
L.control.layers(baseLayers, overlays).addTo(map);
LP
While searching I happened upon this site that shows some of the Leaflet code:
http://ruby-doc.org/gems/docs/l/leaflet-js-0.7.0.3/lib/leaflet/src/control/Control_Layers_js.html
In it I found this condition for the application of autoZIndex:
if (this.options.autoZIndex && layer.setZIndex) {
this._lastZIndex++;
layer.setZIndex(this._lastZIndex);
}
TileLayer is the only layer type that has a setZIndex function, so apparently autoZIndex only works there.
I'm not sure which annoys me more. This incredible limitation or the fact that Leafet documentation doesn't point it out.
At least on 0.7.2, I had to use bringToFront in the callback of map.on('overlayadd'). autoZIndex: false did not work in my case neither. A comment on this issue may explain the reason.
It's not specific to L.GeoJson layers. As far as I can tell, it's true of all Leaflet layers with layer control. The last layer turned on is simply on top. I don't think this is a bug either. It's predictable behavior which I use and depend on when I'm designing maps with layer control...

jqPlot- issue with displaying bar charts -axis padding and inter-bar distance

I'm using the following piece of code to display a set of bar charts in jqPlot. However, I am getting 2 problems:
There is no padding on either side of the range values for the xaxis. The pad attribute seems to have no effect.
When the number of bars is large, the bars overlap on top of each other. The barPadding attribute seems to have no effect.
I looked at this link Having problems with jqPlot bar chart . The answer suggested there was to use a CategoryAxisRenderer. But since I'm using time-series data, I require the DateAxisRenderer.
Please help.
function plotBarGraph(data,myticks,series)
{
$("#placeholder").empty();
$.jqplot('placeholder',data,
{
//stackSeries:true,
seriesDefaults:{
renderer:$.jqplot.BarRenderer,
barMargin:1,
barPadding:0.5
},
axes:
{
xaxis:
{
ticks:myticks,
tickInterval:10,
renderer:$.jqplot.DateAxisRenderer,
pad:2.5,
tickOptions:
{
formatString:'%d-%m-%y'
}
}
},
legend:
{
show:true,
labels:series
}
});
}
In this case if you do not want to use the approach mentioned in the link, you might want to play with min/max values for your date axis.
I think you might also find useful the approach to a similar sort of problem with padding where I chose to set ticks on creation and hide the unwanted ones after the plot is drawn.
Before I can be of any further assistance please provide a sample presenting your problem.
For your second problem, I ran into a similar issue. I set up a selection statement that widened the size of my chart depending on how many bars would be in it. I then made the parent div scrollable (called "dataHolder"), and that solved the problem.
// set div properties depending on how many sets of data are in the
//array so the view is sized properly
$("#chartDiv").html("").css("width", "750px"); // chartDiv's default settings
$("#tabs-6").removeClass("dataHolder").css("width", "900px"); // parent Div's default settings
if (dataArray.length > 10) {
$("#chartDiv").css("width", "1600px");
$("#tabs-6").addClass("dataHolder").css("width", "900px");
} else if (dataArray.length > 6) {
$("#chartDiv").css("width", "1200px");
$("#tabs-6").addClass("dataHolder").css("width", "900px");
}
I found a solution to use Date data with CategoryAxisRenderer. You have to configure de xaxis like follows:
axes: {
xaxis: {
renderer: $.jqplot.CategoryAxisRenderer,
rendererOptions: {
tickRenderer: $.jqplot.DateTickRenderer,
tickOptions: {
formatter: $.jqplot.DateTickFormatter,
formatString:'%d/%m'
}
}
}
}
I hope it helps! ;)