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.
Related
I want to display objects on a map in their exact dimensions.
I have reference data about these objects, containing the length and width of the object in meters, now I need to convert the meters into pixels for the leaflet icon size based on the zoom level.
var meterlength = 50;
var meterwidth = 40;
//convert meters to pixels
var icon = L.divIcon({html:"<svg>..</svg>",iconSize:[xx,yy]});
I found this https://github.com/makinacorpus/Leaflet.GeometryUtil/
GeometryUtils with the length and distance function, but could not get it working.
Any ideas ? Thank you very much !
EDIT:
This answer solved my original problem:
https://gis.stackexchange.com/a/198444
But I'm sacling the icons on map zoom using the "zoomanim" event - unfortunately the map.containerPointToLatLng() Methode applies to the old zoom level not to the new one.
Is there a Workaround ?
If you want your image to represent the object real size, then you should probably better use an L.imageOverlay instead of a marker and trying to adjust its size based on zoom level.
See https://gis.stackexchange.com/questions/171609/resize-divicons-svgs-at-zoom-levels-leaflet
Then you would need to find the appropriate coordinates for your Image Overlay bounds, not pixels.
Now if you really want to change your marker icon size depending on the zoom level, you have several posts on SO and GIS SE that cover this topic.
E.g. Best way to make marker resizable in leaflet
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.
I have a Google Maps canvas that stretches the full width and height of the page. Overlaid on top of it is a header which is fixed height (100 pixels) and a sidebar which is a responsive width (20% + 5% margin).
Fiddle for demonstration: http://jsfiddle.net/L9yjvdLv/1/
The problem I'm facing is making sure that all the markers on the map are visible.
I tried playing around with fitBounds, but the problem is that the map doesn't take into account the overlaid elements, meaning markers will be behind the sidebar or header elements, or very close to them.
How do I zoom and center the map so that all markers are visible in the "usable" area of the map?
You will need to use the map projection and fromLatLngToPoint to translate between coordinates and points to be able to take into account your different overlay elements.
For a full explanation, please check this answer.
function fromLatLngToPoint(latLng) {
var scale = Math.pow(2, map.getZoom());
var nw = new google.maps.LatLng(map.getBounds().getNorthEast().lat(), map.getBounds().getSouthWest().lng());
var worldCoordinateNW = map.getProjection().fromLatLngToPoint(nw);
var worldCoordinate = map.getProjection().fromLatLngToPoint(latLng);
return new google.maps.Point(Math.floor((worldCoordinate.x - worldCoordinateNW.x) * scale), Math.floor((worldCoordinate.y - worldCoordinateNW.y) * scale));
}
My example only has a left sidebar overlay but you should be able to adapt the functions to your needs.
JSFiddle demo
Hope this helps.
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 can't seem to find andthing in the Google Maps V3 docs about creating custom content boxes for Google maps.
Below is an example of a custom content box:
http://www.apple.com/retail/alamoana/map/
My question is... how do you go about changing the default white balloon popup?
Cheers!
Look at the InfoBox toolkit in the v3 utility libraries. I'm using them on dev.mapfaire.com, if you want to take a look.
Personally, i don't use any of google's custom overlay scripts and such. I made a custom php page which hold the iframe, where I can load custom css files, and also I create custom DIVs that go over top of the map, which are then repositioned while dragging when opened.
You can use the "Drag,Dragstart,Dragend" events to help you reposition elements that you have created.
If you have custom markers set onto you page you can get their pixel position with this function:
getPosition: function (marker) {
var map = this.map /* Your map, in my case is part of my controller object linked to this.map */;
var scale = Math.pow(2, map.getZoom());
var nw = new google.maps.LatLng(
map.getBounds().getNorthEast().lat(),
map.getBounds().getSouthWest().lng()
);
var worldCoordinateNW = map.getProjection().fromLatLngToPoint(nw);
var worldCoordinate = map.getProjection().fromLatLngToPoint(marker.getPosition());
var pixelOffset = new google.maps.Point(
Math.floor((worldCoordinate.x - worldCoordinateNW.x) * scale),
Math.floor((worldCoordinate.y - worldCoordinateNW.y) * scale)
);
return pixelOffset; /* Returns the top left pixel of the specified marker on the specified map */
}
And then I use a setPosition function which is used when the window is dragging, it sets your custom element's position to the marker's position.
The dragging event can be set in such manner:
google.maps.addEventListener(map,'drag',function () { setPosition(marker,element); });
The setPosition Function simply gathers the width,height of the element, and the pixel offset associated to the marker using the getPosition(marker) function:
setPosition: function (marker,element) {
var p = this.getPosition(marker),
s = {width:element.offsetWidth,height:element.offsetHeight},
markerOffset = {width:58,height:58};
element.style.left = p.x - (s.width/2) + (markerOffset.width/2) + "px"; /* We set the element's left position to the marker's position + half of our element's width + half of the marker's width's so it is centered */
element.style.top = p.y - s.height - (markerOffset.height) + 10 + "px"; /* We set the element's top position to the marker's position + our element's height + markers height so it is 10px above our marker vertically */
},
Sometimes you just have to think a bit outside the box
That example is using the second version of google maps. That features might not be available in the 3rd one.
But you can add any html code in the infowindows and personalize them. I'm not sure if you can change how they look directly, but you can definitely change how the content looks like.
Edit: I found some sample code: http://gmaps-samples-v3.googlecode.com/svn/trunk/infowindow_custom/infowindow-custom.html
Take a look at gmaps-utility-library-dev and more specific in the ExtInfoWindow utility and PopupMarker utility
As Sudhir pointed out, the Infobox Plugin is one way to do what you want. I've recently answered a similar question about using the infobox plugin for google maps api 3 and provided a complete example.