I'm working with the Leaflet.StyledLayerControl plugin and would like to set my layers so that polygons always get pushed to the back, lines above the polygons, and points above the lines.
I have searched for solutions here but most refer to tilelayers or map panes (which I think only work with another version of leaflet 1.0).
I want to be able to toggle lines on and off and have them always be below points (same with polygons below polylines).
I'm guessing I have to do something with setZIndex or bringToFront() but i'm not sure where to start.
Any help would be appreciated. Thanks.
The easy solution is really to use Leaflet 1.0, which provides you with map.createPane("paneName") functionality. So you create like "polygonsPane", "linesPane" and "pointsPane", that you specify to each of your vector / shape layers using their pane option.
Once set, you can add / remove them to / from map, and they will always be inserted in the specified pane, hence respecting the pane's order.
// Create necessary panes in correct order (i.e. "bottom-most" first).
map.createPane("polygonsPane");
map.createPane("linesPane");
map.createPane("pointsPane");
L.polygon([/* coords */], { pane: "polygonsPane" }).addTo(map);
L.polyline([/* coords */], { pane: "linesPane" }).addTo(map);
L.circleMarker([/* coords */], { pane: "pointsPane" }).addTo(map);
Demo: https://jsfiddle.net/3v7hd2vx/51/
With Leaflet 0.7, you know that all vector layers are part of the same SVG container, being appended when added to map, hence they appear on top of all previously added vectors. Then you have to use bringToFront() and/or bringToBack() to re-sort them to whatever order you need.
You might be interested in that post for that case: https://gis.stackexchange.com/questions/166252/geojson-layer-order-in-leaflet-0-7-5/167904#167904
Related
I set up a PostGIS database that I added in GeoServer via a parameterized SQL view. I used Leaflet to display this layer via wms.
It worked fine until I add GeoWebCache using the url "/geoserver/gwc/service/wms" instead of "/geoserver/wms". I can still see my polygons when I'm at the minimal zoom. But then when I zoom I see only a red polygon and a half of a green polygon and if I zoom again I see only the red polygon. You can see these 3 states on the images below:
I guess this is a problem of tiling: I get the minimal tiles and also some tiles around the red polygon for further zooms but for some reason it seems that the other tiles are not sent.
Here is the code I use to get my wms layer with leaflet:
geoJSONlayer = L.tileLayer.wms("/geoserver/gwc/service/wms", {
layers: 'cartowiki:choix',
format: 'image/png',
transparent: true,
viewparams: 'year:'+(annee+3000)
}).addTo(map);
geoJSONlayer.addTo(map);
Do you have an idea of the problem here ?
Thanks in advance,
The bounding box was the problem indeed. In Geoserver, I had to modify the properties of the layer in 2 places :
I clicked 'Compute from SRS bounds' and then 'Compute from native bounds' in the Bounding Boxes part of the Data section
I erased and created again the available gridsets in the Tile Caching section so that the grid subset bounds would update with the new Bounding Boxes
I hope it can help someone in the future!
I can't figure out why the radiusmap layer (first map shown) and the isochronemap layer (second map shown) don't scale correctly when I zoom in and out. The third map, which has the exact same layers, works just fine.
Here's the code for a) creating the circle and polygon layers and then b) adding them to the radiusmap, the isochronemap and finally, the combined map.
var isochrone = L.polygon(isochronedetails, {color: 'red'});
var radius = L.circle([radiuslat, radiuslng], {fillColor: circleStyle.color[0], fillOpacity: circleStyle.opacity[0],radius: radiusmeters});
radius.addTo(radiusmap);
isochrone.addTo(drivetimemap);
radius.addTo(combinedmap);
isochrone.addTo(combinedmap);
The code that is creating / initializing the three maps is here:
radiusmap = L.map('radiusmap').setView([radiuslat, radiuslng],10);
radiusmap.addControl(new L.Control.Fullscreen());
drivetimemap = L.map('drivetimemap').setView([isochronelat, isochronelng], 10);
drivetimemap.addControl(new L.Control.Fullscreen());
combinedmap = L.map('combinedmap').setView([isochronelat, isochronelng], 10);
combinedmap.addControl(new L.Control.Fullscreen());
And here's a gif of what happens on zoom in/out for each map. You'll see that when the zoom happens for the first two maps, the circle or polygon moves away from the original drawing position. But, on the third map, it behaves correctly.
https://screencast-o-matic.com/watch/cYe1VQx6pZ
What am I doing wrong? I just want the first two maps to behave like the third map. :-)
Simply do not add your layers (namely isochrone and radius) to several maps, but create different layers (possibly with same parameters) for each separate map.
Leaflet makes the assumption for simplicity that when a layer is added to a map, it belongs only to that map.
I am using leaflet.js to create few markers and circles. I am using the below given code to draw circles : -
L.circle([ lat, lng ], 1000, {
color : colorCode,
stroke : false,
fillColor : colorCode,
fillOpacity : 0.7
});
Now if I edit this circle on UI and drag this circle vertically downwards, the circle size increases and vice a versa. Similar issue is with calling the above given method with different lat lngs. The same radius (1000) sized circle get plotted with different sizes on map.
My requirement is to place marker with same radius with same size on map everywhere.
I checked L.circleMarker but it takes radius in pixels and also circleMarkers does not scale in zoomin zoomout events. That is why I can't use circleMarkers.
I changed the crs option to 4326 but no success. I am using imageOverlay not tileset. I have created a fiddle.
http://jsfiddle.net/newBee_/88bdrzkr/12/
Try creating a circle on top area then edit and move it downwards. It's size increases. This is what I want to stop. This will resolve the problem of generating circle of same radius via code in different area of map with same size. Please help.
Please suggest.
Edit:
It looks like this is a bug deep into Leaflet 0.x: L.Circle radius computation uses hard-coded Earth projection rather than the specified CRS. Leaflet 1.0 seems to correctly check for the CRS before using the Earth-related computation.
For your case, simply overriding the faulty method seems to fix it, at least visually.
Demo: http://jsfiddle.net/88bdrzkr/13/
The "corrected" method to include in your script:
L.Circle.include({
_getLngRadius: function () {
return this._getLatRadius();
}
});
Regarding iH8's answer, the trick to override L.CRS.Simple.scale is similar to highly zooming (the 256 factor expands the latLng to much further pixels - any high number will do). At high zoom, you are moving your circle along a very short distance, for which the latitude does not change much. So you do not see any visible difference in radius, even though the bug is still there.
Demo of using just higher zoom, no method override at all: http://jsfiddle.net/kau6g8fk/1/
For your need where the circle looks to be more like a visual aid, any of these 3 solutions is enough.
Edit: the CRS is not the issue at all.
Previous message:
If you use Leaflet for indoor mapping, as your jsFiddle suggests (or any flat type map, as opposed to the projection of a sphere like Earth on to a plane), you could simply use L.CRS.Simple
Striked out this faulty solution as pointed out by Ghybs in his answer
Very weird issue, turns out that overloading L.CRS.Simple's scale method to return 256 * Math.pow(2, zoom) fixes this. Here's a fork of your JSFiddle: http://jsfiddle.net/kau6g8fk/ I'm unsure as to the cause of this issue, it would require more research. Will do if i find the time. Found the solution here: http://codepen.io/mike_beweb/pen/BymKGe
The answer below was given before the poster edited his/her question and showed that the used CRS was L.CRS.Simple while i presumed the default CRS. I'll leave it in tact because it might come in handy for some users:
The size change on drag of your L.Circle's is because of your map's default spherical mercator projection (EPSG:3857). Best explained with an image, here's a map with a graticule overlay on every 10 degrees:
Demo on Plunker: Leaflet 0.7.5 EPSG:3857 Spherical
As you move further from the equator every plane becomes higher. Thus your circle automaticly becomes higher the further north/south you drag it. You could use a equirectangular projection (EPSG:4326), in which every plane has the same size regardless of the distance from the equator:
Demo on Plunker: Leaflet 0.7.5 EPSG:4326 Equirectangular
With equirectangle projection you won't have the problem you're having now but you'll have to change your tileset to one with EPSG:4326 projection and those are hard to come by compared to EPSG:3857 tilesets.
If you're not willing or unable to change projection another solution could be to hack around L.CircleMarker and change the radius of your markers depended on current zoomlevel. But that's rather ugly in my opinion.
I am using Leafletjs. Currently its pretty straight forward, I have a streets view from open maps.
var streets = L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
maxZoom: 18,
attribution: 'Map data © OpenStreetMap contributors'
});
I also have a WMS layer that is coming from a geoserver. It has the standard getFeatureInfo and everything shows up correctly.
L.tileLayer.wms("GEOSERVERURL", {
layers: 'layers',
format: 'image/png'
,transparent: true
}).addTo(map);
The wms layer is also clickable and I use getFeatureInfo to get the info for that layer. The issue is that the user doesn't know its clickable because the cursor never changes when they hover of the wms layer. My question is how do make the cursor change when hovering over the layer?
Has anyone implemented this feature before or have an idea to implement it? The only research I have stumbled across so far has using mouseover on the map and calling getFeatureInfo to tell if its over a layer. However, that seems like it would cause a lot of chatter just to identify cursor area.
EDIT: To clarify, I want the cursor to only change when its hovered over the wms layer that is populated. Although it technically gets applied to the whole map, it only has content on a part of it. Which kind of raises the question of 'Can I limit the wms layer to only the content area and then show a cursor?' Maybe a bounding area or something along those lines?
EDIT 2: Below is an example of what it looks like. The street map parts I want to keep the normal cursor but I want a pointer when hovering over the colored wms map parts.
Set an ID on the tileLayer's container and then use CSS to change the cursor:
Javascript:
var wms = L.tileLayer.wms("GEOSERVERURL", {
layers: 'layers',
format: 'image/png',
transparent: true
}).addTo(map);
wms.getContainer().setAttribute('id', 'wmsContainer');
Stylesheet:
#wmsContainer {
cursor: grab; /* or any other cursor: https://developer.mozilla.org/en-US/docs/Web/CSS/cursor */
}
Note: you need to do this after the layer is added to the map. Before you add it to the map the getContainer method will return undefined.
Edit after question edit and comments:
Unfortunatly that's not possible. At least not as far as i know. Because L.TileLayer.WMS is a layer of images, there is absolutely no way of deducting which tiles have features on them and which are transparent.
What you could do as a workaround is work out the boundaries of your object, use that to create a transparent polygon without stroke and put that over your WMS layer. Polygons are interactive thus you get the cursorchange included, plus as an extra bonus, you can do other fancy stuff like show the outline or something like that on mouseover. I've created a little demo from the WMS example you supplied in the comments.
http://plnkr.co/edit/1HGn6IUzdrn1N5KGazXQ?p=preview
Note that i'm using a GeoJSON layer with one feature instead of a polygon, because it was easier to find the outline of the US in GeoJSON format. But in your case a four point polygon would do the trick just as wel.
Hope that helps, let me know if something isn't clear.
Working on this application which I have broken down here.
http://jsfiddle.net/pPMqQ/81/
In this example I want to
show markers only inside the shape area
allow for zoom of the map and scaling of the shape area
here is my pseudo code
identifyMarkersInShape: function(){
//__ function is invoked every time a shape is drawn/editted
// hides all the markers
//finds the markers inside the given shape
},
bindEvents: function(){
//handle zoom of the map and scale of the path shape
}
Fully working example: http://jsfiddle.net/PDf9G/5/
Edit: Now simplifies polygon before adding it to the map. Editing the polygon works as well.
First, the css and html: I moved the div called #canvas1 after the map and gave it absolute positioning and z-index = 0. I also gave the map the same absolute positioning to ensure that they always line up with each other, and gave it a z-index of 10.
When the draw button is clicked the canvas is moved to the front. The user can use it to draw free form using d3. When they are done the shape they drew is converted to a google maps polygon.
addShapeToBaseMap: function(divCoords) {
var geoCoords = []
for (var i = 0; i < divCoords.length; i++)
geoCoords.push(overlay.getProjection().fromContainerPixelToLatLng(new google.maps.Point(Number(divCoords[i][0]), Number(divCoords[i][1]))));
poly = new google.maps.Polygon({
paths: geoCoords,
strokeColor: '#FF0000',
strokeOpacity: 0.8,
strokeWeight: 2,
fillColor: '#FF0000',
fillOpacity: 0.35
});
poly.setMap(map);
Then we do do the hiding/showing of the markers. Using d3 this way is really silly and defeats the purpose. You need to add the google geometry library to your url to use this (&libraries=geometry). I'm sure there are faster ways and if you're dealing with large datasets you'll want to make this better.
for (var i = 0; i < data.length; i++) {
if (!google.maps.geometry.poly.containsLocation(new google.maps.LatLng(data[i]['lat'], data[i]['lng']), poly)) {
d3.select("#" + data[i]['name']).classed({'hide': true});
} else {
d3.select("#" + data[i]['name']).classed({'hide': false});
}
}
This works because when we appended the markers we added their name as the id on the marker element. The only reason I can see to do this is because the svg gives you better control over styling. Last:
svg.select(".selection").remove()
d3.select("#canvas1").classed({'front': false});
$('.draw').removeClass('highlight');
},
We remove the shape we drew from the drawing layer. If we don't do this, if the user moves the map and then turns the drawing layer back on, the shape will be in the wrong place. Then we move the canvas to the back and turn off the highlighting on the drawing button.
The edit function was taken from your most recent code. If the edit button or polygon is clicked the editing function is turned on on the polygon.
I would also recommend taking a look at Leaflet. The integration with d3 is a bit easier and you can have multiple svg layers, which would allow you to put the drawing layer as a map overlay instead of a separate div.
geojson-utils is a node/browser javascript package that has a bunch of utilities for dealing with geojson paths.
One of the many things that it has is a very solid point in polygon algorithm designed for dealing with geojson paths.
You also might want to consider using Leaflet.js instead of Google Maps, it has a few more tools for going to and from geojson based data. It also has a lot of really useful tools already written like Leaflet.draw which has the code already needed to "draw" these paths on top of the maps.