Google Maps Overlay Layer

How to make multiple Overlays on a single Google Map?

Use panes to layer overlay elements on the map.

This won't work in every scenario, but if you need some type of extra DOM layer between the map base layer and the overlay of the image, it works well. This solution works perfectly for the situation I was in, where I wanted a screen of opacity on top of the underlying map.

You don't create two entirely separate OverlayViews over the same map.

Instead, to do this, assuming you've followed the Custom Overlays Documentation to set up your primary overlay, you add code to the configuration of your OverlayView to make another layer.

In the constructor, you add a holding div for your additional layer, which we'll call layer:

     /* Set up properties to hold the overlay, which is added in onAdd() */
this.layer_ = null
this.div_ = null

Then when initiating the Overlay with the onAdd(), you set up your additional div. The crucial part here is properly using the available panes that google maps exposes as MapPanes for your use:

    MyOverlay.prototype.onAdd = function () {
/* Create a layer in between google and overlay tiles */
const layerDiv = document.createElement('div')
layerDiv.style.backgroundColor = 'white'
layerDiv.style.opacity = '0.5'
layerDiv.style.position = 'absolute'
/* Any other properties here for your additional layer element */

this.layer_ = layerDiv

/* Attach the elements to the proper pane layers of the map */
const panes = this.getPanes()
panes.mapPane.appendChild(layerDiv)
panes.overlayLayer.appendChild(div)

You don't have to touch the draw() function, but in the onRemove() you need to remove your additional overlay pane:

    MyOverlay.prototype.onRemove = function () {
this.layer_.parentNode.removeChild(this.mask_)
this.layer_ = null
this.div_.parentNode.removeChild(this.div_)
this.div_ = null
}

That's it, you should now have an additional overlay appearing between your primary overlay and the underlying map.

google maps overlay layer

The API will overwrite anything inside the DIV you assign to the map as container.
To cover the map you'd need to put your overlay div outside the map div with position:absolute

Can the Google Maps API base map be used as an overlay above a custom map layer?

Here is an example with a custom tile layer as the middle layer:

<script type="text/javascript">
function initMap() {
// Map with everything off but boundaries
var myOptions = {
zoom: 2,
center: new google.maps.LatLng(0, 0),
styles: [
{
featureType: 'poi',
stylers: [
{ visibility: 'off' }
]
},
{
featureType: 'road',
stylers: [
{ visibility: 'off' }
]
},
{
featureType: 'transit',
stylers: [
{ visibility: 'off' }
]
},
{
featureType: 'landscape',
stylers: [
{ visibility: 'off' }
]
},
{
elementType: 'labels',
stylers: [
{ visibility: 'off' }
]
}
]
};

var map = new google.maps.Map(document.getElementById("map"), myOptions);

// Middle layer
var tileLayer = new google.maps.ImageMapType({
getTileUrl: "tiles URL here",
tileSize: new google.maps.Size(256, 256),
isPng: true
});

map.overlayMapTypes.push(tileLayer);

// Top layer with everything off but roads
var roadsLayer = [
{
featureType: 'all',
stylers: [
{ visibility: 'off' }
]
},
{
featureType: 'road',
stylers: [
{ visibility: 'on' }
]
}
];

var roadsType = new google.maps.StyledMapType(roadsLayer, { name: 'roads' });
map.overlayMapTypes.push(roadsType);
}

Getting information from overlapping layers in Google Maps Javascript API

The suggestion with "normal" Google Maps Polygons, KmlLayers, FusionTablesLayers, is to make them not take mouse events (set clickable: false), but it looks like DataLayer Polygons doen't use the clickable: false property of the DataStyleOptions.

One option would be to translate the DataLayer Polygons to google.maps.Polygon objects, setting clickable: false on them, then do a point in polygon analysis on all those polygons to determine which polygon(s) contain the clicked point.

proof of concept fiddle

code snippet:

var map;
var layer1;
var layer2;
var layer3;
var polygons = [];

function initMap() {
map = new google.maps.Map(document.getElementById('map'), {
zoom: 10,
center: {
lat: 41.5224,
lng: 2.1455
}
});

layer1 = new google.maps.Data();
layer2 = new google.maps.Data();
layer3 = new google.maps.Data();

layer1.addListener('addfeature', createPolygons);
layer2.addListener('addfeature', createPolygons);
layer3.addListener('addfeature', createPolygons);

// Load GeoJSONs.
layer1.addGeoJson({
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"properties": {
"name": "tsstwstae",
"desc": "taeae"
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[2.2254180908203125, 41.46717104920911],
[2.1004486083984375, 41.42393631848872],
[2.1838760375976562, 41.362122449727956],
[2.2511672973632812, 41.407200866420744],
[2.2267913818359375, 41.46356925533331],
[2.2254180908203125, 41.46717104920911]
]
]
}
}]
});
layer2.addGeoJson({
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"properties": {
"name": "test1",
"desc": "test1"
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[2.1790695190429688, 41.466399253078876],
[2.167739868164062, 41.41904486310779],
[2.1704864501953125, 41.39226405354582],
[2.2209548950195312, 41.39097623653649],
[2.231597900390625, 41.42393631848872],
[2.2144317626953125, 41.43680680891725],
[2.2034454345703125, 41.459452674486556],
[2.1818161010742188, 41.470772643007564],
[2.1790695190429688, 41.466399253078876]
]
]
}
}, {
"type": "Feature",
"properties": {
"name": "test2",
"desc": "test2"
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[2.256317138671875, 41.46151099757483],
[2.231597900390625, 41.45070407253965],
[2.2487640380859375, 41.42625319507269],
[2.278289794921875, 41.42625319507269],
[2.2683334350585938, 41.45404926555731],
[2.2638702392578125, 41.46408380956508],
[2.256317138671875, 41.46151099757483]
]
]
}
}, {
"type": "Feature",
"properties": {
"name": "test3",
"desc": "test3"
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[2.13134765625, 41.42754031300878],
[2.1018218994140625, 41.39226405354582],
[2.1430206298828125, 41.377066187268866],
[2.182846069335937, 41.380930388318],
[2.1581268310546875, 41.39097623653649],
[2.1639633178710938, 41.40153558289846],
[2.1605300903320312, 41.413638089039786],
[2.1577835083007812, 41.4249660516211],
[2.164306640625, 41.427797733534135],
[2.1361541748046875, 41.430371882652814],
[2.13134765625, 41.42754031300878]
]
]
}
}]
});
layer3.addGeoJson({
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"properties": {
"name": "test1",
"desc": "test1"
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[2.2061920166015625, 41.453534631705814],
[2.1258544921875, 41.429342235252946],
[2.1869659423828125, 41.329904449073865],
[2.300262451171875, 41.368048825311206],
[2.2576904296875, 41.454563895325855],
[2.2247314453125, 41.45816618938139],
[2.2061920166015625, 41.453534631705814]
]
]
}
}, {
"type": "Feature",
"properties": {
"name": "test2",
"desc": "test2"
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[2.2240447998046875, 41.48852043212608],
[2.132720947265625, 41.48569140009698],
[2.1138381958007812, 41.4504467428547],
[2.2724533081054688, 41.47334508750842],
[2.242584228515625, 41.49237800399966],
[2.2240447998046875, 41.48852043212608]
]
]
}
}]
});

layer1.setStyle({
fillColor: 'green',
opacity: 0.5,
strokeWeight: 1,
clickable: false
});

layer2.setStyle({
fillColor: 'red',
opacity: 0.5,
strokeWeight: 1,
clickable: false
});

layer3.setStyle({
fillColor: 'blue',
opacity: 0.5,
strokeWeight: 1,
clickable: false
});

layer1.setMap(map);
layer2.setMap(map);
layer3.setMap(map);

var layers = [];
layers.push(layer1);
layers.push(layer2);
layers.push(layer3);

map.addListener('click', function(event) {
document.getElementById('status').innerHTML = "";
for (var i = 0; i < polygons.length; i++) {
if (google.maps.geometry.poly.containsLocation(event.latLng, polygons[i])) {
document.getElementById('status').innerHTML += "polygon " + i + " name=" + polygons[i].name + " desc=" + polygons[i].desc + "<br>";
}
}
});

function getAllProperties(pointLatLng) {
for (var key in layers) {
var layer = layers[key];
layer.forEach(function(feature) {
var gaGeom = feature.getGeometry();
});
}
}
layer1.setMap(null);
layer2.setMap(null);
layer3.setMap(null);
}
google.maps.event.addDomListener(window, "load", initMap);

function createPolygons(e) {
if (e.feature.getGeometry().getType() == "Polygon") {
var paths = [];
for (var i = 0; i < e.feature.getGeometry().getArray().length; i++) {
var path = [];
paths.push(e.feature.getGeometry().getAt(i).getArray());
}
}
var polygon = new google.maps.Polygon({
paths: paths,
map: map,
clickable: false,
name: e.feature.getProperty('name'),
desc: e.feature.getProperty('desc')
});
polygons.push(polygon);
}
html,
body,
#map {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
}
<script src="https://maps.googleapis.com/maps/api/js?libraries=geometry&key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk"></script>
<div id="status"></div>
<div id="map"></div>

How to make overlaid POIs in google maps accessible

Basics about Google Maps

Google Maps (may) consist of many layers which are hard to verify in the browser console. I marked the outer map layer in the console and copied the whole html which is produced by JavaScript in an editor, so it's easier to verify and to search for expected elements like markers or overlays.

Nevertheless, debugging objects like map, markers or overlays can be done very well by console.log().

Concerning layers with z-index there is, like mentioned in the question, a clear structure. But z-index as well as the content of each layer can be adjusted (or not used) and I doubt that it's useful to show the general structure in detail without concrete use-case. So any according details are directly related to the question.

Overlays fundamentals

Overlays can be done on many levels, there exists also a special class google.maps.GroundOverlay which is sorted directly above the map. To get an overview about objects and methods related to overlay a good start is just to search for the word overlay in the listing with classes and properties.

Bind Overlay to pane

A common way to realize overlays is to create the definition based on google.maps.OverlayView and to bind the overlay to a pane:


class USGSOverlay extends google.maps.OverlayView {
bounds_;
image_;
div_;
constructor(bounds, image) { ... }
onAdd() {
this._div = document.createElement('div');
...
const panes = this.getPanes();
// inding overlay to a pane,
// `mapPane` can be exchanged by a different existing pane:
panes.mapPane.appendChild(this.div_);
...
}
draw() { ... }
onRemove() { ... }
}
overlay = new USGSOverlay(map);
overlay.setMap(map);

This procedure will bind the map to a predefined pane which usually has the z-index like listed in the question (range of default z-index from 100 to 107).

Bind Overlay directly to map

It's also possible to bind an overlay independent of the panes to a map, then it will be directly above the map if no distinct z-index is defined. This method is used in the linked example.
The example is using the prototype so it's a bit more complicated but essentially is done like this:

class MyOverlay extends google.maps.OverlayView {
...
onAdd() {
// NO binding like `panes.mapPane.appendChild(this.div_);`
}
...
}
overlay = new MyOverlay(map);
overlay.setMap(map);

The choice which method is used might have impact on the produced html in the map but in any case has impact to the z-index. So whatever the target of any manipulation is beyond this choice, it has impact on further proceedings.

Fundamentals of POIs and Markers

POIs (Points of interest) in google maps are handled completely differently and have in most cases different z-indexes too. The functionality for the user might be the same though.

POIs (Points of interest)

POIs can be en- or disabled with the settings:

        const styles = {
default: [],
hide: [
{featureType: "poi.attraction", stylers: [{ visibility: "off" }]},
{featureType: "poi.business", stylers: [{ visibility: "off" }]},
{featureType: "poi.government", stylers: [{ visibility: "off" }]},
{featureType: "poi.medical",stylers: [{ visibility: "off" }]},
{featureType: "poi.park",stylers: [{ visibility: "off" }]},
{featureType: "poi.place_of_worship", stylers: [{ visibility: "off" }]},
{featureType: "poi.school", stylers: [{ visibility: "off" }]},
{featureType: "poi.sports_complex", stylers: [{ visibility: "off" }]},
],
};
map.setOptions({ styles: styles["hide"] });

Images of POIs are directly "printed" on the map tiles which are just png-images. POIs can't be moved and beyond a click function to open an info-window they usually have no direct functionality on the map (they might be connected though with advanced map options).

This is an image directly copied from g-map including a POI:

Sample Image

Concerning functionality POIs are a combination of Image and HTML-Markup, which are both not directly combined in the HTML source but logically combined by position on the map.

Markers

Markers can have individual functions, individual design and some can be also dragged to other locations if the map supports it. They have a different standard design and can have a different z-index than POIs, furthermore they have an own API which also allows to change the z-index.

This is a standard marker:

Sample Image

While Images of POIs are always underneath any overlay, Markers can be shown above overlays, which also has some impact to juggling with z-indexes in comparison.

Conclusion so far

There are many layers with different z-indexes and more could probably easily created. Many challenges concerning google maps relate to the z-index and the sorting in the html source, so binding elements to the right layer is likely the solution for many cases.

The challenge

The primary part of the challenge is to create overlays and enabling the clicks on POIs and their info-windows below these overlays. It might be considerable to display the info-windows above the overlays.

The second part of the challenge is to display markers and their info-windows above the overlays. This seems to be much easier than the first part.

What information / experience exists?

This question was how to disable mouse events below an overlay and currently I've the impression it's answer is way to complicated as it would be possible just to raise the z-index of the overlay to avoid mouse events by binding it to a pane. Nevertheless I'm quite glad about question beside answers as the site shines a light on several details.

Also the short example is quite useful to see things in action and to verify some details.

The challenge in details

The example shows that the overlays never prevent click events on POIs below overlays that are bound directly to the map in contrast to any pane.

Nevertheless the info windows can't be closed and so the info windows are a challenge by themselves.

Placing marker on top of the overlay should be easy by binding them to a pane.

So the overlay shall be bound directly to the map, markers to a pane.

Options concerning info windows for POIs are not clear yet, they shall be shown above the overlay or else at least be closable.

It might be still to verify if the behavior of and related to the overlay is always the same if it's built by svg, by html markup or by path i.e with the polygon option.

Comment concerning examples on jsfiddle.net

Local code and code on jsfiddle.net behave a bit different and are not 100% consistent in behavior. So jsfiddle.net is good to show running examples but the code has to be changed perhaps or just used in another variant.
If something is not working on jsfiddle.net, try it on your own server first before commenting.

Step 1

As the linked question was about preventing what I want to achieve, first the individual event handlers in the example / answer have to be deactivated.

Furthermore the definition this._div.style.zIndex = 1000; can be deactivated to get the option to close open info-windows.
Strange seems to be that info-windows are not always overlaid, but sometimes on top of the overlay, sometimes below. This should be consistent, at best above the overlay. Another issue is that the info windows are not always closable but in most cases when I tried (On jsfiddle.net this does not work).

The small changes can be seen here in action.

Here is the full code for testing on the own server, add your own API key in the bottom of the file in the variable "googleApiKey":

<!doctype html>
<html>
<head>
<title>Example for clickevents</title>
<style>
html, body {height: 100%;margin: 0;padding: 0;}
#googleMap {height: 70%; width:100%}
</style>
<script src="https://polyfill.io/v3/polyfill.min.js?features=default"></script>
<script
type="application/javascript"
src="//code.jquery.com/jquery-2.1.3.js"
></script>
</head>
<body>
<div id="googleMap"></div>
<div id="message"></div>

<script>
let map;

function log(msg) {
//console.log(msg);
document.getElementById('message').innerHTML += msg + '<br>';
}

function initMap() {
var mapProp = {
center: new google.maps.LatLng(51.508742, -0.120850),
zoom: 15,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
var map = new google.maps.Map(document.getElementById("googleMap"), mapProp);

let myMarkers = [];
myMarkers['marker1'] = new google.maps.Marker({
position: new google.maps.LatLng(51.506742, -0.120850),
map: map,
title: "MARKER 1",
});
myMarkers['marker2'] = new google.maps.Marker({
position: new google.maps.LatLng(51.510742, -0.120850),
map: map,
title: "MARKER 2",
});
for (currentMarker in myMarkers) {
var marker = new google.maps.Marker({
position: myMarkers[currentMarker].position,
map: map,
title: myMarkers[currentMarker].title,
// icon: icon,
});
}

var infowindow = new google.maps.InfoWindow({
content: 'Welcome to Google! Thanks for using our products and services (“Services”). The Services are provided by Google Inc. (“Google”), located at 1600 Amphitheatre Parkway, Mountain View, CA 94043, United States.By using our Services, you are agreeing to these terms. Please read them carefully.Our Services are very diverse, so sometimes additional terms or product requirements (including age requirements) may apply. Additional terms will be available with the relevant Services, and those additional terms become part of your agreement with us if you use those Services.'
});

myMarkers['marker1'].addListener('click', function () { infowindow.open(map, myMarkers['marker1']); });
myMarkers['marker2'].addListener('click', function () { log('marker2 clicked'); });

MyOverlay.prototype = new google.maps.OverlayView; //extends google.maps.OverlayView {
function MyOverlay(map) {
this._div = document.createElement('div');
this._div.style.background = 'rgba(0, 0, 60, 0.2)';
this._div.style.position = 'absolute';
// this._div.style.zIndex = 1000;
this._div.style.width = '100%';
this._div.style.height = '200px';
this.listeners = [];
this.setMap(map);
}
const overlay = new MyOverlay(map);
// const overlay = new MyOverlay;

MyOverlay.events = [
'mousedown', 'mousemove', 'mouseover',
'mouseout', 'mouseup', 'mousewheel',
'DOMMouseScroll', 'touchstart', 'touchend',
'touchmove', 'dblclick', 'contextmenu'
];
MyOverlay.prototype.onAdd = function () {
var self = this;
this.getPanes().floatPane.appendChild(this._div);
this.listeners = MyOverlay.events.map(function (event) {
console.log({map:map,event:event});
myMarkers['marker1'].addListener('mousedown', function () { log('marker1 clicked'); });
});
};
MyOverlay.prototype.onRemove = function () {
this.getPanes().floatPane.removeChild(this._div);
};
MyOverlay.prototype.draw = function () {
myMarkers['marker1'].addListener('mousedown', function () { log('marker1 clicked'); });
};
overlay.setMap(map);
console.log(overlay);
}
window.initMap = initMap;


googleApiKey = '';
</script>
<script
src="https://maps.googleapis.com/maps/api/js?key=" + googleApiKey + "&callback=initMap&v=weekly&channel=2®ion=DE&language=de"
async defer
></script>
</body>
</html>

So actually my own initial question how to make POIs accessible is answered, I will answer on more details I mentioned above in "The challenge" later by extending this answer.



Related Topics



Leave a reply



Submit