Overlaying a text box on a leaflet.js map - javascript

This might seem a very simple question, but I've searched elsewhere for the answer with no luck!
How do I overlay a simple text box on to a Leaflet map that loads when the map loads (not fixed to any point on a map) - for example, to give a title and more information within the actual map object. Nothing fancy.

I know this is old, but here's some sample code, CSS as necessary:
L.Control.textbox = L.Control.extend({
onAdd: function(map) {
var text = L.DomUtil.create('div');
text.id = "info_text";
text.innerHTML = "<strong>text here</strong>"
return text;
},
onRemove: function(map) {
// Nothing to do here
}
});
L.control.textbox = function(opts) { return new L.Control.textbox(opts);}
L.control.textbox({ position: 'bottomleft' }).addTo(map);

You have two simple options, extend a new L.Control and place it in one of the four corners of the map window with content created inside the onAdd method, or place a L.DivIcon anywhere on the map alongside a L.Marker, by either geographical coordinates or coordinates based upon the dimensions of the container.Making it "box" like would just include a small bit of CSS as you see fit, like some padding,background-color, etc.

Related

In Leaflet, how to load tiles only when clicking on each?

I have a leaflet map consisting of a satellite image with lat-long metadata. My objective is to display a rectangle highlighting the selected tile on mouseclick and fetch the corresponding tile. The L.rectangle method needs bounds to display the rectangle. In order to calculate the SE and NW corners, I use the following function that I found at https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Tile_numbers_to_lon..2Flat.
This is for SE and the same function is changed for getting NW as seen in the above link. These are used as bounds for the rectangle.
//I have a fixed zoom level for my problem :4
function(xtile,ytile,zoom) //here i am giving the tile location coordinates as the input
{
n=2.0**zoom;
lon= (xtile+1)/n*360-180;
lat_rad=Math.atan(Math.Sinh(Math.PI*(1-2*(ytile+1)/n)));
lat=lat_rad(180/Math.PI);
return[-lat,lon]
}
The output comes out to be as follows:
NW -782.67,-112.4
SE -79.163,-89.9
with(suppose) xtile=5 and ytile=14
The actual output however should have been:
NW -66.51,-45
SE -74.01,-22.5
and the corresponding(again,suppose) xtile=5 and ytile=1
So basically, for some tiles , the rectangle shows just fine. But for others, it jumps to some other tile. I have a display window for the selected tileand when the rectangle jumps, neither the selected tile nor the highlighted tile is fetched. I will be really thankful is someone can help me with this.
My objective is to display a rectangle highlighting the selected tile on mouseclick and fetch the corresponding tile.
Then a good approach to the root problem is to use a custom L.GridLayer, since it provides functionality to create empty tiles, attach events to them and handle tile coordinates without the need to handle bounding boxes explicitly.
It would go like this:
L.GridLayer.ClickyLoad = L.GridLayer.extend({
// By default, the container for a whole zoom level worth of visible tiles
// has a "pointer-events: none" CSS property. Override this whenever a new
// level container is created. This is needed for pointer (mouse) interaction.
_onCreateLevel: function(level) {
level.el.style.pointerEvents = 'inherit';
},
// The tiles shall be empty <div>s with some DOM events attached.
createTile: function(coords){
var tile = L.DomUtil.create('div');
tile.style.border = '1px solid black';
// Highlighting the tile on mouse hover is just swag.
L.DomEvent.on(tile, 'mouseover', function(){
tile.style.border = '2px solid red';
});
L.DomEvent.on(tile, 'mouseout', function(){
tile.style.border = '1px solid black';
});
// When a tile is clicked, calculate the URL of the tile (using the same
// logic as L.TileLayer.getTileUrl() ), create a <img> element, and put
// the newly created image inside the empty <div> tile.
L.DomEvent.on(tile, 'click', function(){
var img = L.DomUtil.create('img');
img.src = L.Util.template('https://tile.openstreetmap.org/{z}/{x}/{y}.png', coords);
tile.appendChild(img);
});
return tile;
}
});
// Create instance and add to map
(new L.GridLayer.ClickyLoad()).addTo(map);
See a fully working example here.
As usual with this kind of non-trivial-looking examples, reading through the Leaflet documentation, the tutorial about extending classes, and the Leaflet source code itself is recommended.

LeafletJS L.DivIcon HTML marker text - scale relative to map zoom

So I'm using the latest version of leaflet (v1.0.2), and am trying to dynamically apply text labels to specific lat lng points on a custom (geo aligned) map.
My issue is that I need the text on the map to maintain it's the size (as though the text is actually part of the tile image) when zooming. Using a Marker of any kind results in the text staying at its correct size. If I use something like an image overlay and add an SVG with text in, it scales with the map zoom.
I've noticed that the image overlay has a CSS3 scale added to its transform property when zooming whereas the marker does not.
Can I extend the marker to scale as the image overlay does?
I've already written code that listens to the zoom event and adjusts the font size of markers but this is CPU intensive (especially for mobile browsers) and I don't really want to render the text dynamically within svgs either!
I've provided a demo so that this makes more sense. You can see that example_1 (the marker) maintains it's size however far you zoom in or out. Example_2 (the svg image) scales relative to the map when zooming. This (Example_2) is what I'm trying to get an L.DivIcon with html text content to do!
Any help or suggestions are appreciated!
https://jsfiddle.net/z96L7hdu/
Example Code
HTML
<div id="map" style="width:500px; height:600px;"></div>
JavaScript
var map = L.map('map', {
zoomSnap: 0
}).setView([0, 0], 3);
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
var img = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciICAgICB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiB2aWV3Qm94PSIwIDAgNTAwIDQwIj4gIDx0ZXh0IHg9IjAiIHk9IjAiIGZvbnQtZmFtaWx5PSJWZXJkYW5hIiBmb250LXNpemU9IjM1Ij4gICAgRXhhbXBsZV8yICA8L3RleHQ+PC9zdmc+";
imageBounds = [[-8.636810901898114, -12.135975261193327], [-18.28136415046407, 17.181122017133486]];
L.imageOverlay(img, imageBounds).addTo(map);
var myIcon = L.divIcon({className: 'my-div-icon', html:"Example_1"});
L.marker({lat: 0.7800052024755708, lng: 0.010986328125}, {icon: myIcon}).addTo(map);
Apologies for the late answer, but I thought it was an interesting question. You can indeed extend the L.Marker class, to create markers that resize the font of their DivIcon to match the zoom level:
L.FixedSizeMarker = L.Marker.extend({
options: {
fontSize: 12, // starting size of icon in pixels
zoomBase: 3 // Zoom level where fontSize is the correct size
},
update: function () {
if (this._icon && this._icon.tagName === 'DIV' && this._map) {
let size = this.options.fontSize * Math.pow(2, (this._map.getZoom() - this.options.zoomBase));
this._icon.style.fontSize = size + 'px';
}
return L.Marker.prototype.update.call(this);
}
});
L.fixedSizeMarker = (latlng, options) => new L.FixedSizeMarker(latlng, options);
The code above defines a new FixedSizeMarker, which behaves just like a normal Marker, but if you add a DivIcon to it, it will resize the font. It takes two options, to specify the font size in pixels, and the zoom level that you want that font size to be correct for. In the example in the OP's JSFiddle, you would use it like this:
var myIcon3 = L.divIcon({className: 'my-div-icon', html:"Example_3"});
L.fixedSizeMarker({lat: 0.7800052024755708, lng: -12.135975261193327},
{icon: myIcon3, fontSize: 24, zoomBase: 3}).addTo(map);
When using these markers, it may look better to set {markerZoomAnimation: false} in the map options. The marker size change is otherwise quite obvious when you zoom the map.
const element = marker.getElement();
element.style[L.DomUtil.TRANSFORM] = `scale(3)`;
You can use this to modify the css of a marker

GMAPS API V3 Custom InfoWindow: static position

I really want to know if there's a way to make the InfoWindow display on the center of the screen and not above the marker.
Also, how can I get rid of that predefined crappy speech bubble style of the window itself?
When the marker gets clicked, the InfoWindow shall be displayed in the center of the screen with a width of 720px, a black opaque background and a little content (no speech bubble, no pan, just pure css). I cant find anything coming close to what I need.
I included the infobox.js to try to get what I want, but I got 2 problems with this:
It doesn't take my position: absolute and the given top/left values.
After closing the InfoBox once, it won't open again.
Some code that could be relevant:
// creating the box
var ib'.$counter.' = new InfoBox(myOptions);
// box options - just the style part
var myOptions = { boxStyle: { ,width: "720px" ,position:"absolute" ,top:
"-100px" ,left: "100" } };
// add listener - plane js
google.maps.event.addListener(marker'.$counter.', "click",
function() { ib'.$counter.'.show() });
I would really appreciate any kind of help!
Best regards,
Rellston.

fitBounds() shows whole earth (if map is first hidden and then shown)

I have a bunch or markers, and I want to show only the area containing them. I found a long list of similar questions (see at the bottom of the post for some), but none of the solutions works for me. The LatLngBounds is built correctly, but when I call fitBounds the result will be the following:
Instead of:
Can anybody spot an evident error in my code?
var opt = {
zoom: 8,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
map = new google.maps.Map(document.getElementById("map"),opt);
var box = new google.maps.LatLngBounds();
for(var i=0;i<list.length;i++){
var p = new google.maps.LatLng(list[i].lat,list[i].lon);
var marker = new google.maps.Marker({
position: p,
map: map
});
box.extend(p);
}
map.fitBounds(box);
map.panToBounds(box);
Some of the posts I read and tried (list not comprehensive):
Google Maps v3 - Automating Zoom Level?
Google maps V3 custom marker images and fitBounds()
Google Maps with fitBounds don't zoom
fitbounds() in Google maps api V3 does not fit bounds
Edit: this actually happens if (as I do in my application) the map is at first hidden, and showed only later.
I hide it in this way:
$('#map').hide();
and show it:
$('#map').show(function(){
//this is necessary because otherwise
//the map will show up in the upper left corner
//until a window resize takes place
google.maps.event.trigger(map, 'resize');
});
Any clue as to why this happens and how to prevent it (apart from initialising the map when first shown)?
On a side note, if I set zoom and center when declaring the map object (i.e. I don't use fitBounds()) then the map will show correctly, even after a hide/show.
I can't set zoom and center, though, because the list of points is retrieved elsewhere and I don't know where they are beforehand.
Solved (not in a nice way, though).
What I ended up doing was initialising the LatLngBounds with the points when loading the page, but panning and zooming only when showing the map. In this way it works correctly.
E.g.
var box;
function init(){
var opt = {
zoom: 8,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
map = new google.maps.Map(document.getElementById("map"),opt);
box = new google.maps.LatLngBounds();
for(var i=0;i<list.length;i++){
var p = new google.maps.LatLng(list[i].lat,list[i].lon);
var marker = new google.maps.Marker({
position: p,
map: map
});
box.extend(p);
}
}
and then, later (click on a button for example)
function showMap(){
$('#map').show(function(){
google.maps.event.trigger(map, 'resize');
map.fitBounds(box);
map.panToBounds(box);
});
}
It works, but I don't like to have that global var hanging around. I implement the exact same behavior using OpenLayers, and it works correctly without the need for this hack. If anybody has a better solution, please post it and I will accept it if it works.
Thanks to #Engineer and #Matt Handy for helping me eliminate one possible source of errors.
I tried your code in a fiddle, and it works as expected.
So the reason why your code fails must be in the definition of your datapoints (as already suggested by Engineer). Compare your list definition with mine and check if they are different.
Modify to your needs
map.fitBounds(bounds);
var listener = google.maps.event.addListener(map, "idle", function() {
if (map.getZoom() > 16) map.setZoom(16);
google.maps.event.removeListener(listener);
});
same problem, found the reason is that I hide the map (make the container of the map display: none;) before calling fitbounds()
To expand a bit on #JayThakkar 's answer, this worked for me
google.maps.event.addListenerOnce(map, 'idle', function(){
map.fitBounds(bounds);
});
The addListenerOnce function removes the need to call google.maps.event.removeListener(listener);.
And calling map.fitBounds(bounds) inside the listener let us use the calculated bounds's zoom level.

Google maps V3 custom marker images and fitBounds()

I am trying to get my custom markers to show up on my map after i have used the fitBounds() method to fit the boundaries of the map to the markers themselves.
I have done this by looping over the markers array and then extending the boundaries of the map to incorporate the marker co-ordinates.
This works fine with the stock google maps markers. However, my client wants to use quite large (36px by 57px) marker images for their site. How do i compensate for this when fitting the boundaries of the map?
At the moment when using the custom marker images they do not all fit inside the boundaries set.
Since you already have calculated the bounds, you may just need to extend the bounds to add enough buffer area to include the large images. The formula you can use to calculate or extend a bounds this way is called a convex hull; the Computational Geometry Algorithms Library has a section on 2D Convex Hull Algorithms or there is a JavaScript Quickhull Article that also includes a nifty online example near the bottom of the page. Hope this is helpful -
The cheap answer is to always zoom out one level after fitBounds(). But we can do a bit better.
I like writing hacks. Here I am making the assumption that the size of your marker will never be larger than 36x57. I tested a while back to find that fitBounds() leaves a margin of around 42 px between the edge and the closest marker (maybe not on mobiles), and I'm also assuming you are not repositioning the marker, that is, it will always be displayed above the given coordinate position. If icons run off to the other sides, adjustments are needed.
My hack takes advantage of a function that measures the pixel position of a LatLng (using the container version, I read here that the div version is not reliable with bounds changes).
Since we know the height of the icon, and where the topmost marker is, we can pan the map south a bit if it's determined to be offscreen. In case there's not enough margin below, the only option is to zoom out. My only concern is it will be jerky because it calls for two events: fitBounds and the custom panning/zooming. The only answer then would be to rewrite a custom fitBounds. When I tested manually the events ran smoothly.
http://jsfiddle.net/sZJjY/
Click to add cat icons, right-click to trigger the resize/panning.
Example: place 3-4 kitties, right-click, then purposely place another that goes off the top, right-click again.
function fitIcons() {
var left = 180.0;
var right = -180.0;
var top = -90.0;
var bottom = 90.0;
for (var i = 0; i < markers.length; i++) {
curlat = markers[i].getPosition().lat();
curlng = markers[i].getPosition().lng();
if(curlat > top) { top = curlat; }
if(curlat < bottom) { bottom = curlat; }
if(curlng > right) { right = curlng; }
if(curlng < left) { left = curlng; }
}
var overlay = new google.maps.OverlayView();
overlay.draw = function() {};
overlay.setMap(map);
map.fitBounds(new google.maps.LatLngBounds(
new google.maps.LatLng(bottom, left),
new google.maps.LatLng(top, right)));
topPixels = overlay.getProjection().fromLatLngToContainerPixel(
new google.maps.LatLng(top, right));
bottomPixels = overlay.getProjection().fromLatLngToContainerPixel(
new google.maps.LatLng(bottom, left));
topGap = topPixels.y;
bottomGap = $("#map_canvas").height() - bottomPixels.y;
if(topGap < iconHeight) {
if(bottomGap > iconHeight) {
map.panBy(0, topGap);
}
else {
map.setZoom(map.getZoom() - 1);
}
}
}

Categories