z-Index overlay in google maps version 3 - javascript

I'm trying to get an overlay in google maps api v3 to appear above all markers. But it seems that the google api always put my overlay with lowest z-index priority. Only solution i've found is to iterate up through the DOM tree and manually set z-index to a higher value. Poor solution.
I'm adding my a new div to my overlay with:
onclick : function (e) {
var index = $(e.target).index(),
lngLatXYposition = $.view.overlay.getProjection().fromLatLngToDivPixel(this.getPosition());
icon = this.getIcon(),
x = lngLatXYposition.x - icon.anchor.x,
y = lngLatXYposition.y - icon.anchor.y
$('<div>test</div>').css({ left: x, position: 'absolute', top: y + 'px', zIndex: 1000 }).appendTo('.overlay');
}
I've tried every property I could think of while creating my overlay. zIndex, zPriority etc.
I'm adding my overlay with:
$.view.overlay = new GmapOverlay( { map: view.map.gmap });
And GmapOverlay inherits from new google.maps.OverlayView.
Any ideas?
..fredrik

If anyone was having the same problem as I was, here is my problem and solution:
I needed an OverlayView which would add tooltips to markers, but my popup overlay kept showing behind the markers.
I implemented a subclass of the OverlayView as per the Google documentation:
https://developers.google.com/maps/documentation/javascript/customoverlays
When you write your custom OverlayView.prototype.onAdd function, you need to specify to which Pane to attach your overlay. I just copied the code without actually reading the surrounding explanation.
In their code, they attach the overlay to the overlayLayer pane:
var panes = this.getPanes();
panes.overlayLayer.appendChild(div);
But there are many different MapPanes you can use:
"The set of panes, of type MapPanes, specify the stacking order for different layers on
the map. The following panes are possible, and enumerated in the order in which they are stacked from bottom to top:"
MapPanes.mapPane (Level 0)
MapPanes.overlayLayer (Level 1)
MapPanes.markerLayer (Level 2)
MapPanes.overlayMouseTarget (Level 3)
MapPanes.floatPane (Level 4)
I wanted the overlay to hover over all other info on the map, so I used the floatPane pane and problem solved.
So, instead of :
this.getPanes().overlayLayer.appendChild(div)
you use this :
this.getPanes().floatPane.appendChild(div);

You can't change the zIndex of an OverlayView (it has no such property), but it holds panes that contains DOM nodes. That's where you can use the z-index property;
...
lngLatXYposition = $.view.overlay.getPanes().overlayLayer.style['zIndex'] = 1001;
...

In order to be able to play around with the paneType of the mapLabel class, I added a paneType property to the MapLabel class from google utility library (https://code.google.com/p/google-maps-utility-library-v3/source/browse/trunk/maplabel/src/maplabel.js?r=303).
This is usefull to make the label not to be hidden by a polyline.
Please find the code additions to the mapLabel.js file.
MapLabel.prototype.onAdd = function() {
var canvas = this.canvas_ = document.createElement('canvas');
var style = canvas.style;
style.position = 'absolute';
var ctx = canvas.getContext('2d');
ctx.lineJoin = 'round';
ctx.textBaseline = 'top';
this.drawCanvas_();
var panes = this.getPanes();
if (panes) {
// OLD: panes.mapPane.appendChild(canvas)
var paneType = this.get('paneType');
panes[paneType].appendChild(canvas);
}
};
MapLabel = function (opt_options) {
this.set('fontFamily', 'sans-serif');
this.set('fontSize', 12);
this.set('fontColor', '#000000');
this.set('strokeWeight', 4);
this.set('strokeColor', '#ffffff');
this.set('align', 'center');
this.set('zIndex', 1e3);
this.set('paneType', 'floatPane');
this.setValues(opt_options);
}
Sample code using the paneType:
var mapLabel = new MapLabel({
text: segDoc.curr_value.toFixed(0),
position: new google.maps.LatLng(lblLat, lblLng),
map: map.instance,
fontSize: 12,
align: 'center',
zIndex: 10000,
paneType: 'floatPane',
});
Thanks!

Setting z-index to 104 for the overLay layer seems to be the "magic" number" if you care about interacting with the markers (i.e. dragging markers). Any higher than 104 and you can not interact with the markers. Wondering if there is a less brittle solution...

Use panes.overlayMouseTarget.appendChild
If you want to allow your layer to be targetable through mouse clicks (and use events such as "click" or CSS pseudo ::hover) then you should add your overlay to the map using overlayMouseTarget
var panes = this.getPanes();
panes.overlayMouseTarget.appendChild(this.div_);
Also see:
https://developers.google.com/maps/documentation/javascript/reference?csw=1#MapPanes

Disclaimer: this is a dodgy solution that may stop working at any time and you definitely shouldn't use this in production.
For those looking for a quick and dirty solution, this CSS worked for me:
.gm-style > div:first-child > div:first-child > div:nth-child(4) {
z-index: 99 !important;
}
Use at your own risk!

Related

Leaflet - drawing semitransparent canvas tiles over map

I'm extending Leaflet's GridLayer to show canvas tiles on which I draw some data points - the problem is that after zooming in and out the other zoom layers are still visible, or are partially chopped off in random ways.
E.g. on the left is zoom level 13, on the right is after zooming out to 11 and back to 13 -
How can I show only the current zoom level, and prevent the canvas tiles from getting chopped off?
This is how I ended up fixing it, though there might be better ways...
Here's my GridCanvas class -
L.GridLayer.GridCanvas = L.GridLayer.extend({
onAdd: function (map) {
L.GridLayer.prototype.onAdd.call(this, map);
map.on('zoomend', e => this.onZoomEnd(e.target._zoom));
},
// on zoom end, want to hide all the tiles that are not on this zoom level,
// and show all the ones that are
onZoomEnd: function (zoom) {
Object.values(this._levels).forEach(level => {
level.el.style.display = (level.zoom === zoom) ? 'block' : 'none';
});
},
createTile,
});
The visibility of the tiles also gets set in seemingly random ways - this seemed to fix the problem - adding a class to the canvas elements and setting them to always be visible -
function createTile(coords, done) {
const canvas = L.DomUtil.create('canvas', 'grid-canvas-tile');
...
}
with styles.css:
.grid-canvas-tile {
visibility: visible !important;
}

MapBox GL JS marker offset

I'm using MapBox GL JS to create a map with a custom marker:
var marker = new mapboxgl.Marker(container)
.setLngLat([
datacenters[country][city].coordinates.lng,
datacenters[country][city].coordinates.lat
])
.addTo(map);
However, I seem to have some kind of offset problem with the marker. The thing is: when zoomed out a bit, the bottom of the marker is not really pointing to the exact location:
When I'm zooming in a bit further it reaches its destination and it's pointing to the exact spot.
I really love MapBox GL, but this particular problem is bugging me and I'd love to know how to solve it. When this is fixed my implementation is far more superior to the original mapping software I was using.
From Mapbox GL JS 0.22.0 you're able to set an offset option to the marker. https://www.mapbox.com/mapbox-gl-js/api/#Marker
For example to offset the marker so that it's anchor is the middle bottom (for your pin marker) you would use:
var marker = new mapboxgl.Marker(container, {
offset: [-width / 2, -height]
})
.setLngLat([
datacenters[country][city].coordinates.lng,
datacenters[country][city].coordinates.lat
])
.addTo(map);
New solution for mapbox-gl.js v1.0.0 - Marker objects now have an anchor option to set the position to align to the marker's Lat/Lng: https://docs.mapbox.com/mapbox-gl-js/api/#marker
var marker = new mapboxgl.Marker(container, {anchor: 'bottom');
This should cover most cases and is more reliable than a pixel offset in my experience.
I've found an solution to my problem. It might be somewhat hacky, but it solves the positioning problem of the marker: I'm using a Popup fill it with a font awesome map marker icon and remove it's "tooltip styled" borders:
Javascript:
map.on('load', function() {
var container = document.createElement('div');
var icon = document.createElement('i');
icon.dataset.city = city;
icon.addEventListener('click', function(e) {
var city = e.target.dataset.city;
var country = e.target.dataset.country
flyTo(datacenters[country][city].coordinates);
});
icon.classList.add('fa', 'fa-map-marker', 'fa-2x');
container.appendChild(icon);
var popup = new mapboxgl.Popup({
closeButton: false,
closeOnClick: false
})
.setLngLat([
datacenters[country][city].coordinates.lng,
datacenters[country][city].coordinates.lat
])
.setDOMContent(container)
.addTo(map);
});
CSS:
.map div.mapboxgl-popup-content {
background: none;
padding: 0;
}
.map .mapboxgl-popup-tip {
display: none;
}
I just hope someone comes up with a real solution, because this feels kinda dirty to me. But hey: it does the job just fine!
Mapbox Marker now has an element option see this link Mapbox Marker. So instead of appending the icon HTML to the Div element you can simply add into the options when creating a marker. I found this also gets rid of the offset problem. So using the code above you can do this....
var icon = document.createElement('i');
icon.classList.add('fas', 'fa-map-marker-alt');
icon.style.color = 'blue';
new mapboxgl.Marker(container, {anchor: 'center', offset: [0, 0], element: icon})
Also the CSS for the marker can be updated to allow a pointer
.mapboxgl-marker {
border: none;
cursor: pointer;
}

Best way to make marker resizable in leaflet

I am trying to resize my markers every time the map is zoomed in or out.
Currently I am using this approach:
Iterate all markers in zoomend method.
get current icon size
Perform some calculation to get the new marker size according the zoom size.
set the new dimension to the icon object.
map.on('zoomend', function() {
zoomEndLevel = map.getZoom();
var difference = zoomEndLevel - zoomStartLevel;
console.log("difference in zoom " + difference);
markerArray.forEach(function(marker) {
var icon = marker.options.icon;
var oldX = icon.options.iconSize[0];
var oldY = icon.options.iconSize[1];
var newX = getNewIconAxis(oldX, difference);
var newY = getNewIconAxis(oldY, difference);
console.log(newX + " " + newY);
icon.options.iconSize = [ newX, newY ];
marker.setIcon(icon);
});
});
map.on('zoomstart', function() {
zoomStartLevel = map.getZoom();
});
function getNewIconAxis(value, zoomChange) {
if (zoomChange > 0) {
for (var i = zoomChange; i > 0; i--) {
value = value * 2;
}
} else {
for (var i = zoomChange; i < 0; i++) {
value = value / 2;
}
}
return value;
}
Problem :
This code works fine if I zoom in and out 1 level at once. If I scroll in and out my mouse too frequently then this code given strange outputs. Sometimes the marker size becomes too large or too small.
Question :
1) Is this the only way to make the markers resizable on different zoom levels?
2) If yes then what am I missing here or what changes should be made to make it work perfectly.?
Note : Tagging google maps also because it's more of a logical issue with map (either google or leaflet or mapbox) rather than api specific issue.
Looks like there are several previous posts that you guide you:
Mapbox,leaflet: Increase marker size on Zoom
is there a way to resize marker icons depending on zoom level in leaflet?
https://gis.stackexchange.com/questions/171609/resize-divicons-svgs-at-zoom-levels-leaflet
As for your bug, instead of reading the current icon size value at "zoomstart" event, you might better remember the previous zoom value and read it only at "zoomend" event. I am not sure the "zoomstart" event is fired only once when you scroll (to zoom in/out) successively, while the "zoomend" event may be fired only at the very end.

MapBox: Markers not showing clickable cursor on mouseover

I am moving from leaflet+cloudmade to mapbox and have been doing minor rewrites to my code where necessary. I am refreshing my map and in my previous installment it was easiest to add each marker in to it's own layer and then on refresh to remove all layers and redraw the markers.
Here is my current code:
function setLeafletMarker(lat, lng, iconType, popupHTML) {
popupHTML = typeof popupHTML !== 'undefined' ? popupHTML : "";
var LamMarker = new L.Marker([lat, lng], { icon: iconType }); //.on('click', markerClick); ;
markers.push(LamMarker);
LamMarker.bindPopup(popupHTML);
map.addLayer(LamMarker);
}
I suspect this has something to do with the problem, which is that when I put my mouse cursor over a marker, it stays as a hand (draggable) instead of changing to be a pointy finger, meaning the marker is clickable. Clicking works fine, but it's not very intuitive. How do I change the hand to pointy finger?
Ran into the same problem also. Did a quick check of CSS on the mapbox site, and they seem to fix it using a css rule in their sitewide css file (not map specific). I was able to fix the problem using the same approach, by adding this to my sitewide css.
.leaflet-overlay-pane path,
.leaflet-marker-icon {
cursor: pointer;
}
I have compared the default leaflet.css with the default mapbox.css and leaflet includes this
.leaflet-clickable {
cursor: pointer;
}
while mapbox does not.
One way is you can just add the behavior to the mouseover and mouseout events:
LamMarker.on("mouseover", function(e) {
document.getElementById('map').style.cursor = "pointer";
}).on("mouseout", function(e) {
document.getElementById('map').style.cursor = "grab";
});
In the current mapbox api (2022) this works. I'm not sure if there is a smarter way to do this as the docs are terrible in this department.
map.on('mouseover', 'source-id', e => {
map.getCanvas().style.cursor = 'pointer'
})
map.on('mouseleave', 'source-id', e => {
map.getCanvas().style.cursor = ''
})
This assumes you are adding a source layer to your map as in this example
If your not using source layers, you can target your marker icon via css
.marker svg {
cursor: pointer;
}

Make a color tinted map in Google Maps v3

I have searched around the net, and found several solutions. Including this thread here on SO. However, all of the methods creates a flicker on the screen while zooming in/out.
Is there any way of preventing this? I'm currently applying a ImageMapType as overlay. It works great, but the flickering occurs while zooming.
Are there any other alternatives to apply a color tint to the map, but not the other overlays (markers and such).
This is how I apply my ImageMapType btw:
var overlayTint = new google.maps.ImageMapType({
getTileUrl: function(tile, zoom) {return 'library/img/maptint.png';},
tileSize: new google.maps.Size(512, 512),
opacity: 0.30,
isPng: true
});
map.overlayMapTypes.insertAt(0, overlayTint);
EDIT: I'm using Satellite maps, which implies that Styled Maps are not the way to go either.
I found a solution that works pretty well!
I extended the OverlayView class to make my own custom overlay. This overlay consists of a single <div> that has a half-transparent blue:ish color. The div is screensize*2, and is placed in the middle of the screen.
The reason for being twice as big is because the zoom in/out. While you are zooming in, the overlay gets relative resized to fit the ending position. Since the ending position is smaller, this creates a big gap around the overlay. However, if we make it twice as big as the screen, we avoid this problem!
And as the tip of the iceberg, we hook the drag, dragend and center_changed events to re-positionize this div to the center of the screen. This ensures that the overlay stays in place when the user pans around as well.
Hope that this can help anybody else. Thank you for reading!
Just had to solve this myself. This thread gave me quite a few pointers on where to look, but I arrived on a slightly different solution than OP.
I instead created an overlay at the bounds of Lat/Lng, which is -90, -90 to 90 90. The overlay was again, a simple div with the background set to an rgba color.
Here is my solution in coffeescript:
gm = google.maps
center = new gm.LatLng 50, 0
class colorOverlay extends gm.OverlayView
constructor: (#color, #map) ->
#div = null
#setMap #map
#bounds = new gm.LatLngBounds(new gm.LatLng(-90, -90), new gm.LatLng(90, 90))
onAdd: () ->
col = document.createElement 'div'
col.style.width = '100%'
col.style.height = '100%'
col.style.position = 'absolute'
col.style.background = #color
#div = col
panes = #getPanes()
panes.overlayLayer.appendChild(#div)
draw: () ->
# Overlay Projection
oP = #getProjection()
sw = oP.fromLatLngToDivPixel #bounds.getSouthWest()
ne = oP.fromLatLngToDivPixel #bounds.getNorthEast()
div = #div
div.style.left = sw.x + 'px'
div.style.top = ne.y + 'px'
div.style.width = (ne.x - sw.x) + 'px'
div.style.height = (sw.y - ne.y) + 'px'
map = new gm.Map mapElem,
zoom: 10
mapTypeId: google.maps.MapTypeId.HYBRID
center: center
marker = new gm.Marker
position: center
map: map
title: "Hello World!"
overlay = new colorOverlay 'rgba(37,41,56,0.6)', map

Categories