Related
I'm trying to create a custom overlay that covers a large part of the world map (ideally the whole world - inside the overlay I want to use an SVG or canvas that would only uncover selected parts of the map). Here is an example that demonstrates my issue:
function initMap() {
const map = new google.maps.Map(
document.getElementById("map"), {
zoom: 1,
center: {
lat: 0,
lng: 0
},
}
);
const bounds = new google.maps.LatLngBounds(
new google.maps.LatLng(-20, -100),
new google.maps.LatLng(+20, 100)
);
class CustomOverlay extends google.maps.OverlayView {
bounds_
div_
constructor(bounds, image) {
super();
this.bounds_ = bounds;
this.div_ = null;
}
onAdd() {
this.div_ = document.createElement("div");
this.div_.style.width = '100%'
this.div_.style.height = '100%'
this.div_.style.position = "absolute";
this.div_.style.background = "red"
const panes = this.getPanes();
panes.overlayLayer.appendChild(this.div_);
}
draw() {
const overlayProjection = this.getProjection();
const sw = overlayProjection.fromLatLngToDivPixel(
this.bounds_.getSouthWest()
);
const ne = overlayProjection.fromLatLngToDivPixel(
this.bounds_.getNorthEast()
);
if (this.div_) {
this.div_.style.left = sw.x + "px";
this.div_.style.top = ne.y + "px";
this.div_.style.width = ne.x - sw.x + "px";
this.div_.style.height = sw.y - ne.y + "px";
}
}
onRemove() {
if (this.div_) {
(this.div_.parentNode).removeChild(this.div_);
this.div_ = null;
}
}
}
const overlay = new CustomOverlay(bounds);
overlay.setMap(map);
}
window.initMap = initMap;
#map {
height: 100%;
}
html,
body {
height: 100%;
margin: 0;
padding: 0;
}
<html>
<head>
<title>Rectangle Zoom</title>
</head>
<body>
<div id="map"></div>
<script src="https://maps.googleapis.com/maps/api/js?callback=initMap&v=weekly" defer></script>
</body>
</html>
The problem is: whenever I zoom into an area in the eastern part of the red rectangle (Sri Lanka, Malaysia) the overlay disappears.
When I zoom out and pan the map I can see that the overlay is not disappearing - it's just moving to the next "repeated copy" of the map to the east.
How can I prevent this from happening?
Here are some of my ideas, but I can't find a way to implement any of them:
Disabling map repeating (only show one world map) - restricting the scrolling area doesn't fix the problem, I've tried
Locking my overlay to the correct "copy" of the map or show it on every "copy"
Using something different than a custom overlay (a polygon or rectangle won't work because I want to put an image or custom HTML element inside the map)
EDIT: I've just posted this issue on the Google Issue Tracker - here
I've created a fiddle example here where anyone can check my problem.
That problem is that when two overlays (built with a svg element and a path inside it) are too close, i.e, side by side, and both shapes have irregular limits, then the last shape rendered is overlapping the first one.
Visually you can't appreciate it, but if you add, e.g., a click event to the path dom elements to show their names, the last rendered shape is clickable in al its region, but the first one is not entirely clickable. The marked region in the following picture is not clickable for the bottom picture but it should be:
There is a css rule to change the cursor when it is over a path. You can also see that in the specified region the cursor does not change.
How can I avoid this overlapping? What I want is to do the bottom shape entirely clickable.
Finally I tried to find another solution like getting all elements under the mouse position and determine which path was, at that moment, under the pointer, and execute the action on it. I was looking for a way to implement this solution and I found this post: solution!!, where there is just what I needed.
I think it is the best and more elegant way (at least better than I thought before) to solve my issue. Basically it adds the css rule pointer-events: none; to the top svg element which is blocking other path elements. And to the pathobjects I have added pointer-events: all;. So the css styles should look like this:
svg.overlay {
pointer-events: none;
}
svg.overlay > path {
pointer-events: all;
}
The entire solution (you can see it also in a jsfiddler example):
function initialize() {
var myLatLng = new google.maps.LatLng(42, -111.02783203125);
var mapOptions = {
zoom: 6,
center: myLatLng,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
var map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
//polygon coords for Utah
var path = [
new google.maps.LatLng(41.983994270935625, -111.02783203125),
new google.maps.LatLng(42.00032514831621, -114.01611328125),
new google.maps.LatLng(36.96744946416931, -114.01611328125),
new google.maps.LatLng(37.00255267215955, -109.0283203125),
new google.maps.LatLng(40.97989806962013, -109.0283203125),
new google.maps.LatLng(41.0130657870063, -111.02783203125)
];
// custom overlay created
var overlay = new BW.PolyLineFill(path, map, 'red', '#000', 'original');
// polygon coords for conflict shape
var pathConflict = [
new google.maps.LatLng(42.00032514831621, -114.01611328125),
new google.maps.LatLng(41.983994270935625, -111.02783203125),
new google.maps.LatLng(41.0130657870063, -111.02783203125),
new google.maps.LatLng(40.97989806962013, -109.0283203125),
new google.maps.LatLng(47.00255267215955, -109.0283203125),
new google.maps.LatLng(46.96744946416931, -114.01611328125)
];
var overlayConflict = new BW.PolyLineFill(pathConflict, map, 'white', '#000', 'conflict');
}
///Start custom poly fill code
PolyLineFill.prototype = new google.maps.OverlayView();
function PolyLineFill(poly, map, fill, stroke, name) {
var bounds = new google.maps.LatLngBounds();
for (var i = 0; i < poly.length; i++) {
bounds.extend(poly[i]);
}
//initialize all properties.
this.bounds_ = bounds;
this.map_ = map;
this.$dom = null;
this.poly_ = poly;
this.polysvg_ = null;
this.fill_ = fill;
this.stroke_ = stroke;
this.name_ = name;
// Explicitly call setMap on this overlay
this.setMap(map);
}
PolyLineFill.prototype.onAdd = function() {
//createthe svg element
var svgns = "http://www.w3.org/2000/svg";
var svg = document.createElementNS(svgns, "svg");
svg.setAttributeNS(null, "preserveAspectRatio", "xMidYMid meet");
var def = document.createElementNS(svgns, "defs");
//create the pattern fill
var pattern = document.createElementNS(svgns, "pattern");
pattern.setAttributeNS(null, "id", "lineFill-" + this.name_);
pattern.setAttributeNS(null, "patternUnits", "userSpaceOnUse");
pattern.setAttributeNS(null, "patternTransform", "rotate(-45)");
pattern.setAttributeNS(null, "height", "7");
pattern.setAttributeNS(null, "width", "7");
def.appendChild(pattern);
var rect = document.createElementNS(svgns, "rect");
rect.setAttributeNS(null, "id", "rectFill");
rect.setAttributeNS(null, "fill", this.fill_ || "red");
rect.setAttributeNS(null, "fill-opacity", "0.3");
rect.setAttributeNS(null, "stroke", this.stroke_ || "#000");
rect.setAttributeNS(null, "stroke-dasharray", "7,7");
rect.setAttributeNS(null, "height", "7");
rect.setAttributeNS(null, "width", "7");
pattern.appendChild(rect);
svg.appendChild(def);
//add path to the div
var path = document.createElementNS(svgns, 'path');
path.setAttributeNS(null, 'fill', 'url(#lineFill-' + this.name_ + ')');
path.setAttributeNS(null, 'stroke', '#000');
path.setAttributeNS(null, 'stroke-width', '1');
path.setAttributeNS(null, 'pointer-events', 'all');
this.path_ = path;
svg.appendChild(this.path_);
svg.style.borderStyle = 'none';
svg.style.borderWidth = '0px';
svg.style.position = 'absolute';
svg.style.pointerEvents = 'none';
svg.setAttribute('class', 'polygon');
this.$dom = svg;
// We add an overlay to a map via one of the map's panes.
// We'll add this overlay to the overlayLayer pane.
var panes = this.getPanes();
panes.overlayMouseTarget.appendChild(this.$dom);
var dragging = false;
google.maps.event.addDomListener(this.path_, 'mousedown', function(evt) {
dragging = false;
});
google.maps.event.addDomListener(this.path_, 'mousemove', function(evt) {
dragging = true;
});
var _self = this;
// onclick listener
google.maps.event.addDomListener(this.path_, 'click', function(evt) {
if (dragging) {
return false;
}
alert('clicked on ' + _self.name_);
});
}
PolyLineFill.prototype.AdjustPoints = function() {
//adjust the polygon points based on the projection.
var proj = this.getProjection();
var sw = proj.fromLatLngToDivPixel(this.bounds_.getSouthWest());
var ne = proj.fromLatLngToDivPixel(this.bounds_.getNorthEast());
var points = "";
for (var i = 0; i < this.poly_.length; i++) {
var point = proj.fromLatLngToDivPixel(this.poly_[i]);
if (i == 0) {
points += (point.x - sw.x) + ", " + (point.y - ne.y);
} else {
points += " " + (point.x - sw.x) + ", " + (point.y - ne.y);
}
}
return points;
}
PolyLineFill.prototype.draw = function() {
// Size and position the overlay. We use a southwest and northeast
// position of the overlay to peg it to the correct position and size.
// We need to retrieve the projection from this overlay to do this.
var overlayProjection = this.getProjection();
// Retrieve the southwest and northeast coordinates of this overlay
// in latlngs and convert them to pixels coordinates.
// We'll use these coordinates to resize the DIV.
var sw = overlayProjection.fromLatLngToDivPixel(this.bounds_.getSouthWest());
var ne = overlayProjection.fromLatLngToDivPixel(this.bounds_.getNorthEast());
// Resize the image's DIV to fit the indicated dimensions.
var div = this.$dom;
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';
this.path_.setAttributeNS(null, "d", 'M' + this.AdjustPoints() + 'z');
}
PolyLineFill.prototype.onRemove = function() {
this.div_.parentNode.removeChild(this.div_);
this.div_ = null;
}
window.BW = {};
window.BW.PolyLineFill = PolyLineFill;
///end poly fill code
google.maps.event.addDomListener(window, 'load', initialize);
html,
body {
height: 100%;
margin: 0;
padding: 0;
}
.polygon: {
pointer-events: none;
}
.polygon > path {
cursor: pointer;
pointer-events: all;
}
.polygon > path:active {
cursor: -webkit-grabbing;
}
#map-canvas,
#map_canvas {
height: 100%;
}
#media print {
html,
body {
height: auto;
}
#map_canvas {
height: 650px;
}
}
<script src="http://maps.google.com/maps/api/js?sensor=false&.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<div id="map-canvas"></div>
I am having some difficulties where my listeners appear to cancel each other out only when I set a rectangle to the map, but not when I call an alert or anything else.
This works perfectly:
google.maps.event.addDomListener(document.getElementById("overlay" + me), 'click', function () {
displayMessage(me); //displays the current overlay index on screen (IE 1 of 30)
});
The above simply displays the index of the overlay on the map (IE 1 of 30). It works at every overlay clicked with the proper overlay index.
This not so much:
google.maps.event.addDomListener(document.getElementById("overlay" + me), 'click', function () {
alert("Called");
curOverlayRectangle.setOptions(overlayRectangleOptions); //defined by C# to js
curOverlayRectangle.setBounds(incomingOverlayBounds[me]);
curOverlayRectangle.setMap(map);
alert("Finished");
});
The above is supposed to add a rectangle over the overlay already on the map. What it actually does is add the rectangle for the first overlay clicked, but then if I click another overlay, nothing happens.
It appears that the listener is never called because once I click the first overlay, it goes through and says finished with the rectangle drawn. I then proceed to click another overlay and no alert occurs...
I have been working on this for quite some time, please help! Thanks!
EDIT1:
//get is simply the index
function tempAddListener(get) {
//alert("adding: " + get);
if (document.getElementById("overlay" + get) != null) { //check to see if div is there
google.maps.event.addDomListener(document.getElementById("overlay" + get), 'click', function () {
displayMessage("listener fired at overlay: " + get); //if enabled, works fine
//displayOverlayRectangle(incomingOverlayBounds[get]); //if enabled, listener fires but seems to delete all my other listeners for the overlays
});
} else {
//could not find the div
}
}
Edit2
//took out all defines
//#region geoObjs
var incomingOverlayBounds = [];
var incomingOverlaySourceURL = [];
var incomingOverlayRotation = [];
var incomingOverlayRectangle = [];
function initOverlays(){
//most of these are taken out
incomingOverlayBounds[0] = new google.maps.LatLngBounds( new google.maps.LatLng(29.7883456702236,-82.384843759249), new
incomingOverlayRotation[16] = 0;
incomingOverlayBounds[17] = new google.maps.LatLngBounds( new google.maps.LatLng(29.4715356702236,-82.3839748493845), new google.maps.LatLng(29.51265,-82.33674));
incomingOverlaySourceURL[17] = "http://ufdcimages.uflib.ufl.edu/UF/00/07/17/26/00027/12001_1968_2KK_20.jpg";
incomingOverlayRotation[17] = 0;
incomingOverlayBounds[18] = new google.maps.LatLngBounds( new google.maps.LatLng(29.4584356702236,-82.3840587432067), new google.maps.LatLng(29.49955,-82.33683));
incomingOverlaySourceURL[18] = "http://ufdcimages.uflib.ufl.edu/UF/00/07/17/26/00027/12001_1968_2KK_21.jpg";
incomingOverlayRotation[18] = 0;
incomingOverlayBounds[19] = new google.maps.LatLngBounds( new google.maps.LatLng(29.4431556702236,-82.4158516259991), new google.maps.LatLng(29.48427,-82.36863));
incomingOverlaySourceURL[19] = "http://ufdcimages.uflib.ufl.edu/UF/00/07/17/26/00027/12001_1968_2KK_022.jpg";
incomingOverlayRotation[19] = 0;
incomingOverlayBounds[20] = new google.maps.LatLngBounds( new google.maps.LatLng(29.4593656702236,-82.4157191765652), new google.maps.LatLng(29.50048,-82.36849));
incomingOverlaySourceURL[20] = "http://ufdcimages.uflib.ufl.edu/UF/00/07/17/26/00027/12001_1968_2KK_023.jpg";
incomingOverlayRotation[20] = 0;
incomingOverlayBounds[21] = new google.maps.LatLngBounds( new google.maps.LatLng(29.4736856702236,-82.4151858519302), new google.maps.LatLng(29.5148,-82.36795));
incomingOverlaySourceURL[21] = "http://ufdcimages.uflib.ufl.edu/UF/00/07/17/26/00027/12001_1968_2KK_024.jpg";
incomingOverlayRotation[21] = 0;
incomingOverlaySourceURL[51] = "http://ufdcimages.uflib.ufl.edu/UF/00/07/17/26/00027/12001_1968_2KK_054.jpg";
incomingOverlayRotation[51] = 0;
displayIncomingOverlays();
}
//#endregion
function initialize() {
//initialize google map objects
map = new google.maps.Map(document.getElementById(gmapPageDivId), gmapOptions); //initialize map
initOverlays(); //initialize all the incoming overlays
}
var incomingOverlayBounds = [];
var incomingOverlaySourceURL = [];
var incomingOverlayRotation = [];
var overlays = [];
function displayIncomingOverlays() {
for (var i = 0; i < incomingOverlayBounds.length; i++) {
overlaysOnMap[i] = new CustomOverlay(incomingOverlayBounds[i], incomingOverlaySourceURL[i], map, incomingOverlaySourceURL[i]);
overlaysOnMap[i].setMap(map);
//displayOverlayRectangle(incomingOverlayBounds[i]); //add all the rectangles
}
}
function CustomOverlay(bounds, image, map, rotation) {
//iterate here
overlayCount++;
// Now initialize all properties.
this.bounds_ = bounds;
this.image_ = image;
this.map_ = map;
preservedRotation = rotation;
if (overlayPrevious != null) {
overlayPrevious.setMap(null);
}
// We define a property to hold the image's div. We'll
// actually create this div upon receipt of the onAdd()
// method so we'll leave it null for now.
this.div_ = null;
}
CustomOverlay.prototype.onAdd = function () {
if (overlayPrevious != null) {
overlayPrevious.setMap(null);
}
// Note: an overlay's receipt of onAdd() indicates that
// the map's panes are now available for attaching
// the overlay to the map via the DOM.
// Create the DIV and set some basic attributes.
var div = document.createElement("div");
div.id = "overlay" + overlaysOnMap.indexOf(this);
div.style.borderStyle = 'none';
div.style.borderWidth = '0px';
div.style.position = 'absolute';
div.style.opacity = preserveOpacity;
// Create an IMG element and attach it to the DIV.
var img = document.createElement('img');
img.src = incomingOverlaySourceURL[overlaysOnMap.indexOf(this)]; //this.image
img.style.width = '100%';
img.style.height = '100%';
img.style.position = 'absolute';
div.appendChild(img);
//get the index
var overlayIndex = overlaysOnMap.indexOf(this);
// Set the overlay's div_ property to this DIV
this.div_ = div;
// We add an overlay to a map via one of the map's panes.
// We'll add this overlay to the overlayLayer pane.
var panes = this.getPanes();
panes.overlayLayer.appendChild(div);
//add the listener
tempAddListener(overlayIndex);
};
CustomOverlay.prototype.draw = function () {
// Size and position the overlay. We use a southwest and northeast
// position of the overlay to peg it to the correct position and size.
// We need to retrieve the projection from this overlay to do this.
var overlayProjection = this.getProjection();
// Retrieve the southwest and northeast coordinates of this overlay
// in latlngs and convert them to pixels coordinates.
// We'll use these coordinates to resize the DIV.
var sw = overlayProjection.fromLatLngToDivPixel(this.bounds_.getSouthWest());
var ne = overlayProjection.fromLatLngToDivPixel(this.bounds_.getNorthEast());
// Resize the image's DIV to fit the indicated dimensions.
var div = this.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';
//for a preserved rotation
if (preservedRotation != 0) {
//keepRotate(preservedRotation);
}
};
//CustomOverlay.prototype.onRemove = function () {
// this.div_.parentNode.removeChild(this.div_);
// this.div_ = null;
//};
function tempAddListener(get) {
alert("div: "+document.getElementById("overlay" + get).innerHTML);
alert("adding with index: " + get);
if (document.getElementById("overlay" + get) != null) { //check to see if div is there
google.maps.event.addDomListener(document.getElementById("overlay" + get), 'click', function () {
alert("listener fired at overlay: " + get);
displayOverlayRectangle(incomingOverlayBounds[get]);
//alert(incomingOverlayBounds[get]);
//overlayRectangles[get] = displayOverlayRectangle(incomingOverlayBounds[get]);
//overlayRectangles[get].setMap(map);
});
} else {
//could not find the div
}
}
function displayOverlayRectangle(bounds) {
//2do: set drawing manager, set mode, match listeners of rectangle
var tempOverlayRectangle = new google.maps.Rectangle();
var tempOverlayRectangleOptions = {
strokeColor: "#FF0000",
strokeOpacity: 0.8,
strokeWeight: 2,
fillColor: "#FF0000",
fillOpacity: 0.1,
editable: true,
draggable: true,
//strokeOpacity: 0.2,
//strokeWeight: 1,
//fillOpacity: 0.0,
zindex: 5
};
tempOverlayRectangle.setOptions(tempOverlayRectangleOptions);
tempOverlayRectangle.setBounds(bounds);
tempOverlayRectangle.setMap(map);
google.maps.event.addListener(tempOverlayRectangle, "click", function () {
alert("can't touch this");
});
//return tempOverlayRectangle;
//tempOverlayRectangle.setOptions(tempOverlayRectangleOptions);
//tempOverlayRectangle.setBounds(bounds);
//tempOverlayRectangle.setMap(map);
}
//start this whole mess once
google.maps.event.addDomListener(window, 'load', initialize);
</script>
<div id="googleMap"></div>
UPDATE
It appears that any map code on the listener will invalidate the other listeners. (IE I tried it with the following code and it still only ran once)
google.maps.event.addDomListener(document.getElementById("overlay" + get), 'click', function () {
if (map.getMapTypeId() == 'TERRAIN') {
map.setMapTypeId(google.maps.MapTypeId.ROADMAP);
} else {
map.setMapTypeId(google.maps.MapTypeId.TERRAIN);
}
});
SOLVED
Bottom line, the overlay I was creating could not be accessed appropriately by the DOM. Thus, I created an invisible rectangle to overlay on top of my overlay which can be accessed.
Snippet:
var incomingOverlayBounds = []; //defined in c# to js on page
var incomingOverlaySourceURL = []; //defined in c# to js on page
var incomingOverlayRotation = []; //defined in c# to js on page
var ghostOverlayRectangle = []; //holds ghost overlay rectangles (IE overlay hotspots)
var ghostOverlayRectangleOptions = { //define options for ghost rectangle
strokeColor: "#FF0000", //color doesnt matter
strokeOpacity: 0.0, //make border invisible
strokeWeight: 1, //should not matter?
fillColor: "#FF0000", //color doesnt matter
fillOpacity: 0.0, //make fill transparent
editable: false, //just to be sure?
draggable: false, //just to be sure?
zindex: 6 //perhaps higher?
};
var visibleOverlayRectangleOptions = { //define options for visible rectangle
strokeColor: "#FF0000", //for testing (red)
strokeOpacity: 0.8, //for testing
strokeWeight: 2, //for testing
fillColor: "#FF0000", //for testing (red)
fillOpacity: 0.1, //for testing
editable: true, //sobek standard
draggable: true, //sobek standard
//strokeOpacity: 0.2, //sobek standard
//strokeWeight: 1, //sobek standard
//fillOpacity: 0.0, //sobek standard
zindex: 5 //sobek standard
};
var visibleOverlayRectangle = new google.maps.Rectangle(); //init maybe move to array later
//Displays all the overlays sent from the C# code. Also calls displayGhostOverlayRectangle.
function displayIncomingOverlays() {
//go through and display overlays as long as there is an overlay to display
for (var i = 0; i < incomingOverlayBounds.length; i++) {
overlaysOnMap[i] = new CustomOverlay(incomingOverlayBounds[i], incomingOverlaySourceURL[i], map, incomingOverlayRotation[i]);
overlaysOnMap[i].setMap(map); //set the overlay to the map
displayGhostOverlayRectangle(incomingOverlayBounds[i],i); //add all the ghost rectangles
}
}
//Displays an invisible rectangle on top of the overlay div (creates a hotspot). This rectangle is used as a psuedo listener if the 'overlay div' is clicked. This solved issue of creating listener for overlay div directly.
//Supporting URL: http://stackoverflow.com/questions/17025240/google-maps-listener-only-running-once
function displayGhostOverlayRectangle(ghostBounds,ghostIndex) {
ghostOverlayRectangle[ghostIndex] = new google.maps.Rectangle(); //init rect
ghostOverlayRectangle[ghostIndex].setOptions(ghostOverlayRectangleOptions); //set options
ghostOverlayRectangle[ghostIndex].setBounds(ghostBounds); //set bounds
ghostOverlayRectangle[ghostIndex].setMap(map); //set to map
//create the listener for this ghost rectangle
google.maps.event.addListener(ghostOverlayRectangle[ghostIndex], 'click', function () {
displayVisibleOverlayRectangle(ghostBounds, ghostIndex); //add the visible rectangles
});
}
//Displays the visible rectangle which is used to edit an overlay. Called by the ghost listener.
function displayVisibleOverlayRectangle(bounds, overlayIndex) {
visibleOverlayRectangle.setOptions(visibleOverlayRectangleOptions);
visibleOverlayRectangle.setBounds(bounds);
visibleOverlayRectangle.setMap(map);
}
//Starts the creation of a custom overlay div which contains a rectangular image.
//Supporting URL: https://developers.google.com/maps/documentation/javascript/overlays#CustomOverlays
function CustomOverlay(bounds, image, map, rotation) {
overlayCount++; //iterate how many overlays have been drawn
this.bounds_ = bounds; //set the bounds
this.image_ = image; //set source url
this.map_ = map; //set to map
preservedRotation = rotation; //set the rotation
this.div_ = null; //defines a property to hold the image's div. We'll actually create this div upon receipt of the onAdd() method so we'll leave it null for now.
}
//Continues support for adding an custom overlay
//Supporting URL: https://developers.google.com/maps/documentation/javascript/overlays#CustomOverlays
// Note: an overlay's receipt of onAdd() indicates that the map's panes are now available for attaching the overlay to the map via the DOM.
CustomOverlay.prototype.onAdd = function () {
// Create the DIV and set some basic attributes.
var div = document.createElement("div");
div.id = "overlay" + overlaysOnMap.indexOf(this);
div.style.borderStyle = 'none';
div.style.borderWidth = '0px';
div.style.position = 'absolute';
div.style.opacity = preserveOpacity;
// Create an IMG element and attach it to the DIV.
var img = document.createElement('img');
img.src = incomingOverlaySourceURL[overlaysOnMap.indexOf(this)]; //this.image
img.style.width = '100%';
img.style.height = '100%';
img.style.position = 'absolute';
div.appendChild(img);
// Set the overlay's div_ property to this DIV
this.div_ = div;
// We add an overlay to a map via one of the map's panes.
// We'll add this overlay to the overlayLayer pane.
var panes = this.getPanes();
panes.overlayLayer.appendChild(div);
};
//Continues support for adding an custom overlay
//Supporting URL: https://developers.google.com/maps/documentation/javascript/overlays#CustomOverlays
CustomOverlay.prototype.draw = function () {
// Size and position the overlay. We use a southwest and northeast
// position of the overlay to peg it to the correct position and size.
// We need to retrieve the projection from this overlay to do this.
var overlayProjection = this.getProjection();
// Retrieve the southwest and northeast coordinates of this overlay
// in latlngs and convert them to pixels coordinates.
// We'll use these coordinates to resize the DIV.
var sw = overlayProjection.fromLatLngToDivPixel(this.bounds_.getSouthWest());
var ne = overlayProjection.fromLatLngToDivPixel(this.bounds_.getNorthEast());
// Resize the image's DIV to fit the indicated dimensions.
var div = this.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';
//for a preserved rotation
if (preservedRotation != 0) {
keepRotate(preservedRotation);
}
};
//Not currently used
//Supporting URL: https://developers.google.com/maps/documentation/javascript/overlays#CustomOverlays
CustomOverlay.prototype.onRemove = function () {
this.div_.parentNode.removeChild(this.div_);
this.div_ = null;
};
I'm trying to make some custom Google maps info windows, but I'm getting the issue where markers underneath my custom info window is clickable through the info window.
Here's an example (basically straight from googles example here)
<html>
<head>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
<title>Google Maps JavaScript API v3 Example: Info Window Custom</title>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript">
/* An InfoBox is like an info window, but it displays
* under the marker, opens quicker, and has flexible styling.
* #param {GLatLng} latlng Point to place bar at
* #param {Map} map The map on which to display this InfoBox.
* #param {Object} opts Passes configuration options - content,
* offsetVertical, offsetHorizontal, className, height, width
*/
function InfoBox(opts) {
google.maps.OverlayView.call(this);
this.latlng_ = opts.latlng;
this.map_ = opts.map;
this.offsetVertical_ = -195;
this.offsetHorizontal_ = 0;
this.height_ = 165;
this.width_ = 266;
var me = this;
this.boundsChangedListener_ =
google.maps.event.addListener(this.map_, "bounds_changed", function() {
return me.panMap.apply(me);
});
// Once the properties of this OverlayView are initialized, set its map so
// that we can display it. This will trigger calls to panes_changed and
// draw.
this.setMap(this.map_);
}
/* InfoBox extends GOverlay class from the Google Maps API
*/
InfoBox.prototype = new google.maps.OverlayView();
/* Creates the DIV representing this InfoBox
*/
InfoBox.prototype.remove = function() {
if (this.div_) {
this.div_.parentNode.removeChild(this.div_);
this.div_ = null;
}
};
/* Redraw the Bar based on the current projection and zoom level
*/
InfoBox.prototype.draw = function() {
// Creates the element if it doesn't exist already.
this.createElement();
if (!this.div_) return;
// Calculate the DIV coordinates of two opposite corners of our bounds to
// get the size and position of our Bar
var pixPosition = this.getProjection().fromLatLngToDivPixel(this.latlng_);
if (!pixPosition) return;
// Now position our DIV based on the DIV coordinates of our bounds
this.div_.style.width = this.width_ + "px";
this.div_.style.left = (pixPosition.x + this.offsetHorizontal_) + "px";
this.div_.style.height = this.height_ + "px";
this.div_.style.top = (pixPosition.y + this.offsetVertical_) + "px";
this.div_.style.display = 'block';
};
/* Creates the DIV representing this InfoBox in the floatPane. If the panes
* object, retrieved by calling getPanes, is null, remove the element from the
* DOM. If the div exists, but its parent is not the floatPane, move the div
* to the new pane.
* Called from within draw. Alternatively, this can be called specifically on
* a panes_changed event.
*/
InfoBox.prototype.createElement = function() {
var panes = this.getPanes();
var div = this.div_;
if (!div) {
// This does not handle changing panes. You can set the map to be null and
// then reset the map to move the div.
div = this.div_ = document.createElement("div");
div.style.border = "0px none";
div.style.position = "absolute";
div.style.background = "url('http://gmaps-samples.googlecode.com/svn/trunk/images/blueinfowindow.gif')";
div.style.width = this.width_ + "px";
div.style.height = this.height_ + "px";
var contentDiv = document.createElement("div");
contentDiv.style.padding = "30px"
contentDiv.innerHTML = "<b>Hello World!</b>";
var topDiv = document.createElement("div");
topDiv.style.textAlign = "right";
var closeImg = document.createElement("img");
closeImg.style.width = "32px";
closeImg.style.height = "32px";
closeImg.style.cursor = "pointer";
closeImg.src = "http://gmaps-samples.googlecode.com/svn/trunk/images/closebigger.gif";
topDiv.appendChild(closeImg);
function removeInfoBox(ib) {
return function() {
ib.setMap(null);
};
}
google.maps.event.addDomListener(closeImg, 'click', removeInfoBox(this));
div.appendChild(topDiv);
div.appendChild(contentDiv);
div.style.display = 'none';
panes.floatPane.appendChild(div);
this.panMap();
} else if (div.parentNode != panes.floatPane) {
// The panes have changed. Move the div.
div.parentNode.removeChild(div);
panes.floatPane.appendChild(div);
} else {
// The panes have not changed, so no need to create or move the div.
}
}
/* Pan the map to fit the InfoBox.
*/
InfoBox.prototype.panMap = function() {
};
function initialize() {
var myOptions = {
zoom: 8,
center: new google.maps.LatLng(-33.397, 150.644),
mapTypeId: google.maps.MapTypeId.ROADMAP,
sensor: 'true'
}
var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
var marker = new google.maps.Marker({
position: new google.maps.LatLng(-34, 150),
map: map
});
google.maps.event.addListener(marker, "click", function(e) {
var infoBox = new InfoBox({latlng: marker.getPosition(), map: map});
});
var m2 = new google.maps.Marker({
position: new google.maps.LatLng(-33.5, 150.5),
map: map
});
google.maps.event.addListener(m2, "click", function(e) {
var infoBox = new InfoBox({latlng: m2.getPosition(), map: map});
});
}
</script>
</head>
<body style="margin:0px; padding:0px;" onload="initialize()">
<div id="map_canvas" style="width:100%; height:100%"></div>
</body>
</html>
You can see I'm making two markers, one just to the north east of the other. Click the bottom one then observe how the other marker is clickable through the marker (right near the 'W').
How can I fix this?! I've tried altering the z-index but that didn't seem to help.
This is using api v3 btw.
For posterity, you want to disable a bunch of events on the marker pop-up dom, this was the prescribed method, even though it feels like overkill. You could alternatively cycle through all the markers present apart from the one clicked and disable clicking on them (actually that's probably a better solution in hindsight). Depends on how many markers you have perhaps, I had a lot.
Something like this (transcribed here without testing from my coffeescript so...)
// All in a google.maps.OverlayView subclass.
...
this.listeners = new Array(); // save listeners for unbinding later
...
this.cancelEvents = function(){
events = ['mousedown', 'mousemove', 'mouseover',
'mouseout', 'mouseup', 'mousewheel',
'DOMMouseScroll', 'touchstart', 'touchend',
'touchmove', 'dblclick', 'contextmenu'];
// Note, don't disable 'click' if you want to be able to click links in the dom. Some things we're disabling here will can effect how your user might interact with the popup (double click to select text etc)
for(var i = 0; i < events.length; i++){
var event = events[i];
this.listeners.push(
google.maps.event.addDomListener(
this.popup_dom_element,
event,
function(ev){
e.cancelBubble = true;
if(e.stopPropagation){
e.stopPropagation();
}
}
);
);
}
}
this.onAdd = function (){
// build your html popup
var html_code = "...";
// easy way to get a dom element but probably over kill if its your only JQ
this.popup_dom_element = $(html_code);
this.cancelEvents();
}
this.onRemove = function(){
// any other removal code you have
...
for(var i = 0; i < this.listeners.length; i++){
// remove our event listeners
google.maps.event.removeListener(this.listeners[i]);
}
}
Try and use the MapPane overlayMouseTarget instead of floatPane.
If you don't want people to be able to click on a marker, could you call marker.setClickable(false)
i had the same problem and its actually very easy:/
if u are using jquery just set
$(div).bind("click",function(e) {
return false;
});
in the InfoBox.prototype.createElement function. This should help.
Cheers
use:
google.maps.event.addDomListener
instead of
google.maps.event.addListener
This question already has answers here:
Google Maps API 3 fitBounds padding - ensure markers are not obscured by overlaid controls
(8 answers)
Closed 6 years ago.
I have a full screen Google map with HTML/CSS toolbars overlaid on the map, and a set of map markers.
Is there a way to ensure there is enough padding between the markers and the edges of the map, so that no markers are obscured by the toolbars?
(Codepen in case the code below doesn't work)
function initMap() {
var map = new google.maps.Map(document.getElementById('map'), {
draggable: true,
streetViewControl: false,
zoomControl: false
});
var marker1 = new google.maps.Marker({
position: {lat: 37, lng: -121},
map: map,
});
var marker2 = new google.maps.Marker({
position: {lat: 39.3, lng: -122},
map: map,
});
var bounds = new google.maps.LatLngBounds();
bounds.extend(marker1.position);
bounds.extend(marker2.position);
map.fitBounds(bounds);
}
#map {
height: 640px;
width: 360px;
}
#overlays {
position: absolute;
height: 50px;
width: 340px;
background: white;
margin: -80px 10px;
text-align: center;
line-height: 50px;
}
/* Optional: Makes the sample page fill the window. */
html, body {
height: 100%;
margin: 0;
padding: 0;
}
<html>
<head>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no">
<meta charset="utf-8">
<title>Simple markers</title>
</head>
<body>
<div id="map"></div>
<div id="overlays">Controls / Order pizza / ETA / etc.</div>
<script async defer
src="https://maps.googleapis.com/maps/api/js?&callback=initMap">
</script>
</body>
</html>
The problem is this:
UPDATE I've tried adding a control as documented at Custom controls, but the map isn't exactly aware of it - see this fiddle forked from the Maps custom control example. One of the markers is still obscured by the control.
Way late I know.. but I found this searching for a similar issue, but not under controls. If you are trying to push things out from under an control bar, the best way is to create an empty div the same size as your overlaid control and place it in the same spot using the google commands so that maps is aware of it. For example I have a control bar across the top of my map that is 50px tall and I do this.
var padder = document.createElement('div');
padder.style.height = '52px';
padder.style.width = '100%';
map.controls[google.maps.ControlPosition.TOP_CENTER].push(padder);
PHP to make your map fit into your google maps canvas:
a. create a maxmin frame around your imagine wiht maxLat, minLat, maxLng and minLng from everything that you want to show.
$queryMaxMin = 'SELECT max(Nr_Coordinate_Latitude) AS maxLat, min(Nr_Coordinate_Latitude) AS minLat, max(Nr_Coordinate_Longitude) AS maxLng, min(Nr_Coordinate_Longitude) AS minLng FROM coordinate ';
$resultMaxMin = mysql_query($queryMaxMin);
if (count($resultMaxMin) > 0) {
$rowMaxMin = mysql_fetch_array($resultMaxMin);
include ('distanceGeoPoints.php');
$distance = distanceGeoPoints($rowMaxMin['maxLat'], $rowMaxMin['maxLng'], $rowMaxMin['minLat'], $rowMaxMin['minLng']);
}
b. then take the diagonal (maxLat, minLng) to (minLat, maxLng) and calculate the distance using the geometry.spherical library or any algorithm you like - the earth is NOT spherical girls and boys, different models are better equiped to calculate area/distance in your region of the world (wikipedia has a great article on algorithms)
function distanceGeoPoints ($lat1, $lng1, $lat2, $lng2) {
$earthRadius = 3958.75;
$dLat = deg2rad($lat2-$lat1);
$dLng = deg2rad($lng2-$lng1);
$a = sin($dLat/2) * sin($dLat/2) +
cos(deg2rad($lat1)) * cos(deg2rad($lat2)) *
sin($dLng/2) * sin($dLng/2);
$c = 2 * atan2(sqrt($a), sqrt(1-$a));
$dist = $earthRadius * $c;
// from miles
$meterConversion = 1609;
$geopointDistance = $dist * $meterConversion;
return $geopointDistance;
}
c. depending on your canvas's dimensions, set up a zoom factor array (this is mine):
$zoomFactor[0] = null;
$zoomFactor[1] = null;
$zoomFactor[2] = null;
$zoomFactor[3] = null;
$zoomFactor[4] = null;
$zoomFactor[5] = 2000000;
$zoomFactor[6] = 1000000;
$zoomFactor[7] = 500000;
$zoomFactor[8] = 250000;
$zoomFactor[9] = 120000;
$zoomFactor[10] = 60000;
$zoomFactor[11] = 30000;
$zoomFactor[12] = 15000;
$zoomFactor[13] = 7500;
$zoomFactor[14] = 3500;
$zoomFactor[15] = 2000;
$zoomFactor[16] = 1100;
$zoomFactor[17] = 500;
$zoomFactor[18] = null;
$zoomFactor[19] = null;
$zoomFactor[20] = null;
d. then create a routine that takes the distance you got in step b and check it against array:
// zoom factor establish
$zoomFactorFinal = '';
if (($distance > 500) && ($distance < 2000000)) {
include ('zoomFactor.php');
for ($i=20;$i>0;$i--) {
if (!is_null($zoomFactor[$i])) {
if ($distance < ($zoomFactor[$i])) {
// save until distance is smaller than
$zoomFactorFinal .= '&z='.$i;
$zoomInt = $i;
$i = 0;
//echo 'SUCESSO '.$zoomFactorFinal;
}
}
}
} else {
if ($distance <= 500) {
$zoomFactorFinal .= '&z=16';
$zoomInt = 16;
}
if ($distance >= 2000000) {
$zoomFactorFinal .= '&z=2';
$zoomInt = 2;
}
}
f. finally, append the $zoomFactorFinal to your googlemaps URL if you are embedding or use a variation of setZoom() method on your canvas display