Pane events inconsistent between OSX and Windows for Safari - google-maps-api-3

Using Safari on OSX, the Google Map v3 implementation of panes is inconsistent and is preventing marker click events from functioning depending on what pane(s) are being used.
If you enable the develop menu and then override the User Agent to report as Safari for Windows, the implementation then matches all other browsers (FF, IE, Chrome, etc.). This seems to indicate that it is an issue within the Google API as all that changed was the User Agent string.
Incidentally, adding a marker to the overlayPane (#6) actually adds the marker to the overlayShadow (#2) pane instead. So it is not currently possible to add anything to pane 6 on any browser or platform.
Using the following example, the issue can be observed by hovering over each marker. If the marker receives the hover event, it will turn green.
<style type="text/css">
html, body, #map_canvas
{
margin: 0;
padding: 0;
height: 100%;
}
</style>
<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?sensor=false"></script>
<script type="text/javascript">
var map;
function initialize() {
var myOptions = {
zoom: 7,
center: new google.maps.LatLng(-34.397, 150.644),
mapTypeId: google.maps.MapTypeId.ROADMAP
};
map = new google.maps.Map(document.getElementById('map_canvas'), myOptions);
for (var i = 0; i <= 5; i++) {
var m = new marker(i);
m.setMap(map);
}
}
google.maps.event.addDomListener(window, 'load', initialize);
function marker(index) {
this._latlng = new google.maps.LatLng(-34.397 + (index * .1), 150.644 + (index * .1));
this._div = null;
this._index = index;
}
marker.prototype = new google.maps.OverlayView();
marker.prototype.onAdd = function () {
var div = document.createElement('DIV');
var label = document.createTextNode(this._index + 1);
div.appendChild(label);
div.id = this._index + 1;
div.style.border = "1px solid black";
div.style.backgroundColor = "red";
div.style.width = "20px";
div.style.height = "20px";
div.style.position = "absolute";
google.maps.event.addDomListener(div, 'mouseover', function () {
this.style.backgroundColor = "green";
});
google.maps.event.addDomListener(div, 'mouseout', function () {
this.style.backgroundColor = "red";
});
this._div = div;
var panes = this.getPanes();
switch (this._index) {
case (0):
panes.overlayLayer.appendChild(div);
break;
case (1):
panes.overlayShadow.appendChild(div);
break;
case (2):
panes.overlayImage.appendChild(div);
break;
case (3):
panes.floatShadow.appendChild(div);
break;
case (4):
panes.overlayMouseTarget.appendChild(div);
break;
case (5):
panes.overlayShadow.appendChild(div);
break;
}
}
marker.prototype.draw = function () {
var projection = this.getProjection();
if (projection) {
var div = this._div;
var point = projection.fromLatLngToDivPixel(this._latlng);
if (point && div != null) {
div.style.left = point.x - 10 + 'px';
div.style.top = point.y + -10 + 'px';
}
}
}
</script>
<div id="map_canvas"></div>
Has anyone found a way around this issue, or is there a fix pending?

Regarding the part of your question where you say "the Google Map v3 implementation of panes is inconsistent and is preventing marker click events from functioning depending on what pane(s) are being used," the problem appears to be that you may be attempting to make use of mouse events on panes where mouse events are not intended to be used:
pane 1: overlayLayer - from the api-doc: It may not receive DOM events.
pane 2: overlayShadow - from the api-doc: It may not receive DOM events.
Regarding the part of your question that is focused on the MapPanes:
Incidentally, adding a marker to the overlayPane (#6) actually adds
the marker to the overlayShadow (#2) pane instead. So it is not
currently possible to add anything to pane 6 on any browser or
platform.
First, there is no overlayPane; if you are talking about pane 6, from bottom to top, I believe you meant to say: floatPane. Also, I think the problem is actually a bug in your switch statement code:
switch (this._index) {
case (0):
panes.overlayLayer.appendChild(div);
break;
case (1):
panes.overlayShadow.appendChild(div);
break;
case (2):
panes.overlayImage.appendChild(div);
break;
case (3):
panes.floatShadow.appendChild(div);
break;
case (4):
panes.overlayMouseTarget.appendChild(div);
break;
case (5):
// This code is a duplicate of the code in case(1):
panes.overlayShadow.appendChild(div);
// I believe what you want here is:
panes.floatPane.appendChild( div );
break;
}
I hope this is helpful -

Related

How to overlay a raster image type with Google Maps API?

I've got a Google Maps API on my web site like this:
map = new google.maps.Map(document.getElementById("map"),
{
tilt:0
,mapTypeId: google.maps.MapTypeId.SATELLITE
,mapTypeControlOptions: {
mapTypeIds: ["satellite", "terrain","roadmap"],
style: google.maps.MapTypeControlStyle.DROPDOWN_MENU,
position: google.maps.ControlPosition.TOP_CENTER
}
}
);
I would like to add a new raster overlay to this map.
This is the external overlay that I would like to add:
external jpeg
As you can see, there are 6 parameters that compose the URL to get the JPEG that I would like to overlay to Google Maps:
https://it-it.topographic-map.com/
?_path=api.maps.getOverlay
&southLatitude=43.40402777777778
&westLongitude=12.41375
&northLatitude=43.47263888888889
&eastLongitude=12.613194444444446
&zoom=13
&version=202211041528
4 parameters (Southlat, Westlng, Nordlat and Eastlng) that are the bounds of the image, the zoom level and version.
I would like to have some suggestions to write a JS function to add this layer to my Google Maps instance.
I tried looking at maptype image overlay example but I can see that there are only 2 coordinates and not 4, and I cannot understand how tiles works, and if this is compatible with my external image source.
The Topographic-map.com service indeed needs the 4 lat/lng coordinates and the zoom level so you can get an image of whatever dimensions you need.
Therefore the easiest method would be to use a Custom Overlay.
Based off the example linked above, you can create a map, get its bounds, calculate the 4 coordinates (and the zoom level) you need to request the image.
The idea is that you need to recreate the overlay with the new bounds and zoom level every time the user pans the map or zooms in/out, which is why I have used the idle map event listener in the below example.
Also with such a solution, it will work whatever the map size / aspect ratio.
let map;
let overlay = {};
function initMap() {
const mapOptions = {
center: new google.maps.LatLng(46.102284, 7.357985),
zoom: 8,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
map = new google.maps.Map(document.getElementById("map-canvas"), mapOptions);
// Listen for idle map event
google.maps.event.addListener(map, "idle", function() {
// Check if overlay already exists
if (overlay.hasOwnProperty('map')) {
// Remove overlay
overlay.setMap(null)
}
// Get the map bounds and needed coordinates and zoom level
let bounds = map.getBounds();
let southLat = bounds.getSouthWest().lat();
let northLat = bounds.getNorthEast().lat();
let eastLng = bounds.getNorthEast().lng();
let westLng = bounds.getSouthWest().lng();
let zoom = map.getZoom();
// Build the image URL
let srcImg = `https://it-it.topographic-map.com/?_path=api.maps.getOverlay&southLatitude=${southLat}&westLongitude=${westLng}&northLatitude=${northLat}&eastLongitude=${eastLng}&zoom=${zoom}&version=202211041528`;
// Create the overlay
overlay = new TopoOverlay(bounds, srcImg, map);
});
TopoOverlay.prototype = new google.maps.OverlayView();
}
/** #constructor */
function TopoOverlay(bounds, image, map) {
// Initialize all properties.
this.bounds_ = bounds;
this.image_ = image;
this.map_ = map;
// 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;
// Explicitly call setMap on this overlay.
this.setMap(map);
}
/**
* onAdd is called when the map's panes are ready and the overlay has been
* added to the map.
*/
TopoOverlay.prototype.onAdd = function() {
let div = document.createElement('div');
div.style.borderStyle = 'none';
div.style.borderWidth = '0px';
div.style.position = 'absolute';
// Create the img element and attach it to the div.
let img = document.createElement('img');
img.src = this.image_;
img.style.width = '100%';
img.style.height = '100%';
img.style.position = 'absolute';
div.appendChild(img);
this.div_ = div;
// Add the element to the "overlayLayer" pane.
let panes = this.getPanes();
//panes.overlayLayer.appendChild(div);
this.getPanes().overlayMouseTarget.appendChild(div);
};
TopoOverlay.prototype.draw = function() {
// We use the south-west and north-east
// coordinates of the overlay to peg it to the correct position and size.
// To do this, we need to retrieve the projection from the overlay.
let overlayProjection = this.getProjection();
// Retrieve the south-west and north-east coordinates of this overlay
// in LatLngs and convert them to pixel coordinates.
// We'll use these coordinates to resize the div.
let sw = overlayProjection.fromLatLngToDivPixel(this.bounds_.getSouthWest());
let ne = overlayProjection.fromLatLngToDivPixel(this.bounds_.getNorthEast());
// Resize the image's div to fit the indicated dimensions.
let 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';
div.style.opacity = 0.75;
};
// The onRemove() method will be called automatically from the API if
// we ever set the overlay's map property to 'null'.
TopoOverlay.prototype.onRemove = function() {
this.div_.parentNode.removeChild(this.div_);
this.div_ = null;
};
#map-canvas {
height: 180px;
}
<div id="map-canvas"></div>
<script defer src="//maps.googleapis.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk&callback=initMap"></script>
It doesn't work in the snippet because of Uncaught DOMException: Blocked a frame with origin "null" from accessing a cross-origin frame.
Not sure why I get this.
Working code here in a JSFiddle.

Custom map overlay heremaps js api v3

I am trying to switch a project from here maps 2.5.4 to 3.0.5.
I have a map with a custom animated image overlay. In 2.5.4 it is realized via ImageProvider:
var imageProvider = new nokia.maps.map.provider.ImageProvider({
opacity: 0.8,
getBoundingBox: function() {
return new nokia.maps.geo.BoundingBox(
new nokia.maps.geo.Coordinate(55.599073, 3.550307),
new nokia.maps.geo.Coordinate(47.27036232672, 15.434621365086)
)},
getUrl: function() {
return images[index]; // return the current image
},
updateCycle: 86400,
cache: new nokia.maps.util.Cache(100)
});
//add listener to show next image
imageProvider.addListener("update", function(evt) {
index++;
}
In v3.0.5 there is no ImageProvider and I didn't find another solution in the api documentation. Does anyone know how to realize it in v3 ?
Yup, there seems to be no ImageProvider (yet?).
You can hack it in, though:
// assuming you have a H.Map instance call "map"
// that's where we want the image
var rect = new H.geo.Rect(51, 12, 54, 15),
// that's the images we want
images = [
'http://newnation.sg/wp-content/uploads/random-pic-internet-06.jpg',
'http://newnation.sg/wp-content/uploads/random-pic-internet-04.jpg',
'http://newnation.sg/wp-content/uploads/random-pic-internet-06.jpg'
],
current = 0,
// that the image node we'll use
image = document.createElement('img');
// you could probably use CSS3 matrix transforms to improve performance
// but for demo purposes, I'lluse position:absolute and top/left for the image
image.style.position = "absolute";
image.style.opacity = "0.8";
// this function updates the image whenever something changes
var update = function() {
// project the rectangle's geo-coords to screen space
var topLeft = map.geoToScreen(rect.getTopLeft());
var bottomRight = map.geoToScreen(rect.getBottomRight());
// calculate top/left and width/height
var offsetX = topLeft.x;
var offsetY = topLeft.y;
var width = bottomRight.x - topLeft.x;
var height = bottomRight.y - topLeft.y;
// set image source (update is also called, when we choose another image)
image.src = images[current];
// set image position and size
image.style.top = offsetY + "px";
image.style.left = offsetX + "px";
image.style.width = width + "px";
image.style.height = height + "px";
};
// append the image
map.getViewPort().element.appendChild(image);
// set initial values
update();
// update whenever viewport or viewmodel changes
map.getViewPort().addEventListener('sync', function() {
update();
});
map.getViewModel().addEventListener('sync', function() {
update();
});
// zoom to rectangle (just to get the images nicely in view)
map.setViewBounds(rect);
// start the image change interval
setInterval(function() {
current = (current + 1) % 3;
update();
}, 3000);

Subpixel issues with an animated sprite in odd zoom levels of Safari and FF

I have the following fiddle which distills an issue I am having with a larger project
http://jsfiddle.net/zhaocnus/6N3v8/
in Firefox and Safari, this animation will start having a jittering effect left and right on odd zoom levels (zoom in/out using Ctrl+/- or Cmd+/- on Mac). I believe this is do to sub-pixel rendering issues and the differences between the various browsers round up or down pixels during the zoom calculations, but I have no idea how to fix it and am looking for any suggestions.
I can't use more modern CSS3 animation features as I need to support legacy browsers like IE7.
(code from fiddle below, can't seem to post without it, although not sure it makes sense without CSS and HTML)
// js spritemap animation
// constants
var COUNTER_MAX = 9,
OFFSET = -50,
FRAMERATE = 100;
// variables
var _counter = 0,
_animElm = document.getElementById('animation'),
_supportBgPosX = false;
// functions
function play() {
// update counter
_counter++;
if (_counter > COUNTER_MAX) {
_counter = 0;
}
// show current frame
if (_supportBgPosX) {
_animElm.style.backgroundPositionX = (_counter * OFFSET) + 'px';
} else {
_animElm.style.backgroundPosition = (_counter * OFFSET) + 'px 0';
}
// next frame
setTimeout(play, FRAMERATE);
}
// check if browser support backgroundPositionX
if (_animElm.style.backgroundPositionX != undefined) {
_supportBgPosX = true;
}
// start animation
play();
Instead of moving the background to the new frame, have a canvas tag re-draw the frame. The canvas tag handles sub-pixel interpretation independent of the browser and because of this you not only control the render (browser agnostic) but also solve the jitter issue as it's being re-drawn frame-by-frame into the canvas' dimensions in realtime.
Zooming is specifically tough because there's no reliable way to detect the zoom level from the browser using jQuery or plain-ole javascript.
Check out the demo here: http://jsfiddle.net/zhaocnus/Gr9TF/
*Credit goes to my coworker zhaocnus for the solution. I'm simply answering this question on his behalf.
// js spritemap animation
// constants
var COUNTER_MAX = 14,
OFFSET = -200,
FRAMERATE = 100;
// variables
var _counter = 0,
_sprite = document.getElementById("sprite"),
_canvas = document.getElementById("anim-canvas"),
_ctx = _canvas.getContext("2d"),
_img = null;
// functions
function play() {
// update counter
_counter++;
if (_counter > COUNTER_MAX) {
_counter = 0;
}
// show current frame
_ctx.clearRect(0, 0, _canvas.width, _canvas.height);
_ctx.drawImage(_img, _counter * OFFSET, 0);
// next frame
setTimeout(play, FRAMERATE);
}
function getStyle(oElm, strCssRule) {
var strValue = '';
if (document.defaultView && document.defaultView.getComputedStyle) {
strValue = document.defaultView.getComputedStyle(oElm, null).getPropertyValue(strCssRule);
} else if (oElm.currentStyle) {
var strCssRule = strCssRule.replace(_styleRegExp, function (strMatch, p1) {
return p1.toUpperCase();
});
strValue = oElm.currentStyle[strCssRule];
}
return String(strValue);
}
function initCanvas(callback) {
var url = getStyle(_sprite, 'background-image');
url = url.replace(/^url\(["']?/, '').replace(/["']?\)$/, '');
_img = new Image();
_img.onload = function(){
_ctx.drawImage(_img, 0, 0);
callback();
};
_img.src = url;
}
// start animation
initCanvas(play);

creating a Placemarks that can be hidden

I have been trying to create a Placemark that I can hide and show (like turning visibility on and off) on demand (on click)... I am using this to make the placemark:
function placemark(lat, long, name, url, iconsrc){
var placemark = ge.createPlacemark(name);
ge.getFeatures().appendChild(placemark);
placemark.setName(name);
// Create style map for placemark
var icon = ge.createIcon('');
if(iconsrc == "0")
icon.setHref('http://maps.google.com/mapfiles/kml/paddle/red-circle.png');
else{
icon.setHref(iconsrc);
}
var style = ge.createStyle('');
style.getIconStyle().setIcon(icon);
if(iconsrc != "0")
style.getIconStyle().setScale(2.5);
placemark.setStyleSelector(style);
// Create point
var point = ge.createPoint('');
point.setLatitude(lat);
point.setLongitude(long);
//point.setAltitudeMode(1500);
placemark.setGeometry(point);
google.earth.addEventListener(placemark, 'click', function(event) {
// Prevent the default balloon from popping up.
event.preventDefault();
var balloon = ge.createHtmlStringBalloon('');
balloon.setFeature(placemark); // optional
balloon.setContentString(
'<iframe src="'+ url +'" frameborder="0"></iframe>');
ge.setBalloon(balloon);
});
}
I have tried everything... from this:
function hidePlacemark(name){
var children = ge.getFeatures().getChildNodes();
for(var i = 0; i < children.getLength(); i++) {
var child = children.item(i);
if(child.getType() == 'KmlPlacemark') {
if(child.getId()== name)
child.setVisibility(false);
}
}
}
to using this ge.getFeatures().removeChild(child);
can anyone point me to the right direction on creating a function that will allow me to turn the visibility on/off on demand please.
Your hidePlacemark function is missing some {} in your final IF statement
if(child.getId()== name)
you have
function hidePlacemark(name){
var children = ge.getFeatures().getChildNodes();
for(var i = 0; i < children.getLength(); i++) {
var child = children.item(i);
if(child.getType() == 'KmlPlacemark') {
if(child.getId()== name)
child.setVisibility(false);
}
}
}
make it
function hidePlacemark(name){
var children = ge.getFeatures().getChildNodes();
for(var i = 0; i < children.getLength(); i++) {
var child = children.item(i);
if(child.getType() == 'KmlPlacemark') {
if(child.getId()== name) {
child.setVisibility(false);
}
}
}
}
HOWEVER ------- you are better off doing this as it is much faster as you don't need to loop through ALL your placemarks
function hidePlacemark(name) {
var placemark = ge.getElementById(name);
placemark.setVisibility(false);
}
I think the plain ge.getFeatures().removeChild(placemark); works.
I played with this GooglePlayground, and just added the following code to line 8 (that is empty in this GooglePlayground Sample):
addSampleButton('Hide Placemark', function(){
ge.getFeatures().removeChild(placemark);
});
Clicking the button Hide Placemark hides the placemark like a charm here. Any chances your problem is somewhere else in your code?

google map marker image sprite

This is my first post here, hope someone can help.
I am building a mapping website using google maps.
The map shows upto 14 different icons based on a type category
I understand the theory of sprite images and the reduction in server calls these have so I have one image conating all the markers.
Question:
How do I set the position of the sprite, but only call the image once?
I have seen this post
Google map marker sprite image position
but that only shows changing the image for one marker
This is the code
//set the image once
var img = {
url:'images/site/type_sprite.png',
size: new google.maps.Size(32,32),
origin: new google.maps.Point(0,0)
};
idx = 0;
for (var i = 0; i < l; i++) {
lat = parseFloat(markers[i].getAttribute("lt"));
lng = parseFloat(markers[i].getAttribute("ln"));
var type = markers[i].getAttribute("typ");
if ((lat < latUpper && lat > latLower) && (lng < lngUpper && lng > lngLower) && (jQuery.inArray(type,filter_arr)>=0)) {
//change the position of the icon sprite depending on type
switch(type) {
case 'AA':
img(origin, new google.maps.Point(0,0));
break;
case 'AC':
img(origin = new google.maps.Point(0, 42));
break;
case 'ACF':
img(origin= new google.maps.Point(0,84));
break;
default:
img(origin= new google.maps.Point(0,0));
}
var id = markers[i].getAttribute("id");
var name = markers[i].getAttribute("nme");
//var icon = 'images/pins/' + type + '.png';
var point = new google.maps.LatLng(lat, lng);
var marker = new google.maps.Marker({
map: map,
position: point,
title: name,
icon: img
});
//add to valid array
markersArray.push(marker);
//build infowindow string
str = '<div class="iw_h">' + id + ' ' + name + '</div>';
bindInfoWindow(marker, map, infoWindow, str);
bounds.extend(point);
map.fitBounds(bounds);
idx++;
}
} //end of loop
}
If I put the code
var img = {
url:'images/site/type_sprite.png',
size: new google.maps.Size(32,32),
origin: new google.maps.Point(0,0)
};
inside the loop, doesn't that mean the image is repeatedly downloaded (and it will be a bigger image than an individual icon) thus defeating the object of using a sprite?
This is the site btw
http://www.searchforsites.co.uk/full_screen.php

Resources