I have a situation where I have a map with a custom legend formatted as either an SVG or a PNG. The legend is always placed in the bottom left corner but can be quite large (user can turn it off and on).
The map also has many markers. Each marker will have a tooltip, which can also be large-ish. Tooltips show when the mouse is hovering over the marker. The problem arises when a user hovers over a marker close to the legend - the tooltip appears behind the legends. I'd like to make it so the popups appear above the legend. So, from bottom to top: marker, legend, marker popup.
Here is a JSFiddle https://jsfiddle.net/e51mydwa/9/ to describe what I mean. I add the legends in the same way, although the < div id="legend"> tag contains a < img> or < svg> in reality.
<div id="map">
<div id="legend">
I am Legend
</div>
</div>
I've had a look at http://leafletjs.com/examples/choropleth/ , but as you can see by inspecting the DOM, this will suffer the same problem, as the legend is added into the same div as the leaflet controls, which is always above the map layers (as it should be, controls should always be at the top).
I've also tried inserting the legend into a div which is on a sibling layer to the popup containing layer. This fixes the z-index issue, however the parent div of both of these contains a transform which changes as the user drags the map around - meaning the legends change places and aren't static.
Any and all suggestions appreciated.
This requires some heavy hacking, due to the architecture of the Leaflet layers and controls.
One possible approach is to make a custom layer class which stays in a static position, by repositioning its pixel offset at every change of the map's view.
I heartily recommend reading the Leaflet tutorials, in particular the one about map panes and the one about custom layers, to understand how this works.
// Create a 'static' map pane
L.Map.addInitHook(function(){
this.createPane('static');
this.getPane('static').style.zIndex = 675;
});
// Define a custom layer class
L.Layer.StaticOverlay = L.Layer.extend({
onAdd: function(map) {
this._map = map;
var pane = map.getPane('static');
this._container = L.DomUtil.create('div');
pane.appendChild(this._container);
// styling, content, etc
this._container.style.background = 'white';
this._container.style.width = '100px';
this._container.style.height = '50px';
this._container.innerHTML = 'Hi!'
map.on('move zoom viewreset zoomend moveend', this._update, this);
this._update();
},
onRemove: function(map) {
L.DomUtil.remove(this._container);
map.off('move zoom viewreset zoomend moveend', this._update, this);
},
_update: function() {
// Calculate the offset of the top-left corner of the map, relative to
// the [0,0] coordinate of the DOM container for the map's main pane
var offset = map.containerPointToLayerPoint([0, 0]);
// Add some offset so our overlay appears more or less in the middle of the map
offset = offset.add([340, 220]);
L.DomUtil.setPosition(this._container, offset);
}
});
When that's defined, you can simply
var static = new L.Layer.StaticOverlay().addTo(map);
Obviously there are some bits missing, such as how to position the overlay properly (get the map pixel size with getSize(), do the proper arithmetic), and how to set the contents of the overlay with some custom options in the layer constructor.
These are left as an exercise to the reader :-)
See a working example here.
Related
I have a map that fills the screen, and a horizontal overlay of non-map content displayed in the bottom portion of the screen. I want to display a polyline on the map so that it as large as possible within the map view but not hidden below the overlaid content.
Below is what I am trying and it nearly works but gives different results depending on the zoom / position of the map's current view. I need something independent of the current view of the map.
// `map` is the leaflet map
// `polyline` is a leaflet polyline
function fitBounds (latLngBounds, offsetY) { // offsetY in pixels
var zoom, southeast, southeastOffset, newBounds;
if (offsetY) {
zoom = map.getBoundsZoom(latLngBounds);
southeast = map.project(latLngBounds.getSouthEast(), zoom);
southeastOffset = new L.Point(southeast.x, southeast.y + offsetY);
newBounds = latLngBounds.extend(map.unproject(southeastOffset, zoom));
}
map.fitBounds(newBounds || latLngBounds);
}
var contentHeight = 350;
// this will be calculated and is the distance from the top of the
// overlaid content to the bottom of the map
fitBounds(polyline.getBounds(), contentHeight);
The map.getBoundsZoom(latLngBounds) and project/unproject seem to return different values when the map is panned or zoomed differently. I understood from the docs that they'd be independent of the current map view.
Am I using the methods wrong, or is there a different strategy to achieve what I need? Thanks.
getBoundsZoom is dependent on the current map view port size. Therefore if you test with different sizes (e.g. your map container fills the whole page width and you have varying browser window width, possibly because of the console), the results will be different.
If you are sure the map container size has not changed, and you can reproduce the problem on JSBin / Plunker / JSFiddle, then there might be a bug in Leaflet; feel free to report it in the issue tracker.
I figured out how to create a polygon in a custom pane using the Working with Map Panes tutorial and this Stack Overflow question with its associated Fiddle.
Can I switch the polygon to a different (custom) pane after it has been created?
I create a polygon in a custom pane like so:
// create custom pane
mymap.createPane('polygonView');
var polygonViewPane = mymap.getPane('polygonView');
polygonViewPane.style.zIndex = 300;
// with custom renderer
var myrenderer = L.svg({
pane: polygonViewPane
});
// create polygon in a custom pane (note the `renderer:myrenderer` or `pane:polygonViewPane` both work)
var myPoly = L.polygon([
[51.509, -0.08],
[51.503, -0.06],
[51.51, -0.047]
], {
fillOpacity: 1,
pane: polygonViewPane
}).addTo(mymap);
It would seem like I should be able to switch the pane with something like:
// changes pane in options.pane, but appearance on the map is same
myPoly.setStyle({pane: polygonViewPaneTop});
// also does not work (command fails without output in console?)
myPoly.setStyle({renderer: myrenderer2});
but neither of these are working. I can switch the zIndex of the whole pane with polygonViewPane.style.zIndex = 800;, but I will usually have multiple polygons on that main pane, and I only want to bring one to the front of all of my layers.
https://jsfiddle.net/kbkxf220/
EDIT:
Updated Fiddle incorporating IvanSanchez's answer:
https://jsfiddle.net/kbkxf220/3/
// THIS WORKS; polygon switches pane and `myPoly.options.pane` shows new pane.
myPoly.removeFrom(mymap);
myPoly.setStyle({pane: polygonViewPaneTop, renderer: myrenderer2});
myPoly.addTo(mymap);
Note that you need to switch renderer for the polygon to switch panes on the map view (see inspect element) and switch pane for myPoly.options.pane to properly display the new pane.
No, Leaflet doesn't allow to change panes on the fly.
Remove the layer from the map, change its options, then re-add it.
If your problem is bringing lines/polygons on top of each other, remember that you can call bringToFront() on them.
So I tried to draw grid line on my map and I found a good example on google api documentation here : https://developers.google.com/maps/documentation/javascript/examples/maptype-base
it works , now I have another problem in every area or rectangle which built by grid line, I want them to have a listener on click event and then zoom to area that has been clicked. I have tried like this
google.maps.event.addListener(map, "click", function (e) {
var latLng = e.latLng;
map.setCenter(new google.maps.LatLng(latLng.lat(), latLng.lng()));
map.setZoom(17);
});
it works either, but as you can see the latitude and longitude are the exact location where the cursor / pointer clicked, it's not in the middle of the rectangle or area it means the map after zoomed in is wrong. Could anyone help me with this?
I think the problem is because you are using the overlay(as your grid line), when using the tile overlay Google Maps API breaks up the imagery at each zoom level into a set of square map tiles arranged in a grid. When a map moves to a new location, or to a new zoom level, the Maps API determines which tiles are needed and translates that information into a set of tiles to retrieve.
For example each zoom level increases the magnification by a factor of two. So, at zoom level 1 the map will be rendered as a 2x2 grid of tiles. At zoom level 2, it's a 4x4 grid. At zoom level 3, it's an 8x8 grid, and so on.
So when you zoom in, the coordinates that you click is not always in the middle because tile overlay is not set because of your coordinate.
Check this page for more information about overlay.
You can also check this SO question for more information.
I want center my marker on popup open.. and centering map not in marker latlng, but on center of marker and popup!
The problem is that popup has dinamic content(loaded on click).
The map size is full display size in a mobile device!
I'm just used autoPanPadding option in popup but not sufficient
Refer to follow picture:
Using fitzpaddy's answer I was able to make this code which works and is much more flexible.
map.on('popupopen', function(e) {
var px = map.project(e.target._popup._latlng); // find the pixel location on the map where the popup anchor is
px.y -= e.target._popup._container.clientHeight/2; // find the height of the popup container, divide by 2, subtract from the Y axis of marker location
map.panTo(map.unproject(px),{animate: true}); // pan to new center
});
Ciao Stefano,
This is untested pseudocode, but Leaflet project/unproject functions should provide assistance.
i.e;
// Obtain latlng from mouse event
var latlng;
// Convert latlng to pixels
var px = project(latlng);
// Add pixel height offset to converted pixels (screen origin is top left)
px.y -= mypopup.height/2
// Convert back to coordinates
latlng = unproject(px);
// Pan map
map.panTo(latlng,{animate: true});
This depends on zoom scale being constant during calculation, so you might be required to pan the map and then calculate the pan offset to update correctly (using animation, this will only be a gentle transition).
Good luck!
Here's an easy solution:
First you center the map to your marker.
map.setView(marker.latLng);
Then you open the popup.
var popup.openOn(map); = L.popup()
.setLatLng(marker.latLng)
.setContent(dynamic-content)
.openOn(map);
Leaflet will automatically pan the map so the popup fits on the map. To make it look more beautiful you can add a margin-top to the popup with CSS.
My very simple solution keeps the current zoom level as well for better usability.
map.on('popupopen', function (e) {
map.setView(e.target._popup._latlng, e.target._zoom);
});
I'm making a project for the school and I need to resize the marker icons depending on zoom level in a leaflet map, Is there an easy way to accomplish this? Any tutorial on the web? Thanks in advance for the help!!!
In order to change the size of the markers when you zoom in/out, you'll need to handle the event.
map.on('zoomend', function() { });
The zoomend event will be called whenever the map has finished zooming in or out. See the API here.
Now, inside this function, you can call your custom code in order to change the size of the markers. For example, let's say you wanted to take a simple approach and set the size of a circle marker equal to the size of the maps zoom level. See the API for a CircleMarker here
// Create some marker that will be resized on the map zooming
var myMarker = new L.CircleMarker([10,10], { /* Options */ });
map.on('zoomend', function() {
var currentZoom = map.getZoom();
myMarker.setRadius(currentZoom);
});
Now whenever the map zooms in or out, the size of the marker will change.
I'm not sure what Stophace is referring to regarding circleMarkers not changing size, but, adding to the approved answer... if you want to resize circleMakers or change any other styling options (I find it helpful to change the weight along with radius), you can use the following approach:
map.on('zoomend', function() {
var currentZoom = map.getZoom();
var myRadius = currentZoom*(1/2); //or whatever ratio you prefer
var myWeight = currentZoom*(1/5); //or whatever ratio you prefer
layername.setStyle({radius: myRadius, weight: setWeight});
});
layername will be replaced with the name of whatever layer you have which contains circleMarkers... and of course you can change the fractions to your liking to suit your needs.
I'm guessing the OP's school project is finished, but I hope this helps others who have the same question!