Disable drag inertia / momentum on Google Maps V3 - google-maps

Is there a way to disable drag inertia on Google Maps V3? Seems like it should be a MapOption, but I can't find any way to do this.

I ran into this same problem today, with some custom Div's floating above the map needing to be re-positioned on Map Movement. My repositioning worked fine as long as the user came to a complete stop after dragging before letting the mouse go (so there would be no momentum), but if one just dragged quickly and released the div's would end up a bit off.
To fix this, I hooked into the drag event and the idle event:
var map = /* All the map config */
var stickyCenter = map.getCenter();
/* ... Code ... */
google.maps.event.addListener(map, 'drag', function(){ stickyCenter = map.getCenter(); });
google.maps.event.addListener(map, 'idle', function() { map.setCenter(stickyCenter); });
What happens is that after you've dragged and the map has come to a stop (after the momentum is done) the map 'snaps' back into place.
If the snapping is too sudden, one could probably panTo or animate the movement in some way. Hope that helps, it's not perfect, but it's a way to reverse the momentum from a drag event.

Use the undocumented option disablePanMomentum e.g.:
new google.maps.Map(document.getElementById(id), {
disablePanMomentum: true,
backgroundColor: 'none',
disableDefaultUI: true,
center: {
lat: 40.674,
lng: -73.945
},
zoom: 2,
...

This cannot be done with Maps API V3 at this time. Consider filing a feature request here:
http://code.google.com/p/gmaps-api-issues/

I'm pretty sure it's possible to at least counter-act the momentum. Somebody please correct me if I'm wrong! For the sake of simplicity, for instance, let's say your map container has a width of 100vw and a height of 100vh. You simply call getCenter() in a function that is called by a mouseup event, and then use those coordinates to immediately setCenter()?
function initMap(){
...
var win = window;
google.maps.event.addDomListener(win, 'mouseup', setCoords);
function setCoords(){
var x = myMap.getCenter();
var lat = x.lat();
var lng = x.lng();
myMap.addListener('center_changed', function(lat, lng){
myMap.setCenter(new google.maps.LatLng(lat, lng));
};
};
The code above actually works, but the kicker is that it only works once until the browser becomes seemingly overloaded and produces the error Uncaught RangeError: Maximum call stack size exceeded.
Not sure what to do now.
More info about this here.

This one works just fine for me:
map.addListener("dragend", () => map.setCenter(map.getCenter()));

Related

Show loading status during loading of a GeoJSON using Gmaps

How can I intercept the loading of a GeoJSON using loadGeojson function Gmaps ? I'm carrying a gejson of MultiLineString but takes a few seconds to appear on the screen and I need to add a loading for the user to wait for charging
function load(){
google.maps.event.addListener(myLayer, 'addfeature', function (e) {
map.setCenter(e.feature.getGeometry().getAt(0).getAt(0));
});
myLayer.loadGeoJson("/data/myLayer.json");
myLayer.setMap(map);
myLayer.setStyle(function(feature) {
return {
strokeColor: "#B00000",
strokeOpacity:0.8,
strokeWeight: 4
};
});
}
Ivan your answer was the solution to my problem. Thank you! But still have a doubt only works when I create a new map object:
......
var map = new google.maps.Map(document.getElementById('map'), mapOptions);
cicloviasLayer.setMap(map);
I do not want to create a new map but plot in an existing map, how do I get the loading run in this case?
If you take a look at the API reference, you'll see that loadGeoJson has a callback function as the third parameter. So you can show a spinner before the call to loadGeoJson and hide it right after the callback is invoked.
As described in this SO answer:
Use CSS to add loading giff image:
#map_canvas {background: transparent url(images/ajax-loading.gif) no-repeat center center;}
It will dissappear once map is loaded, as shown in jsfiddle.

google maps markers and mouse movement

I have a map loaded and a listener:
map = new google.maps.Map(document.getElementById('mapdiv'),opts);
google.maps.event.addListener(map,'mousemove',function(event) {...
All fine. And I also have a mouse click event being captured to relay coordinates to a database.
But I've added waypoints to the map
for (var wp in waypoints) {
var wpOptions = {strokeColor: waypoints[wp].clr,strokeOpacity: 0.5,strokeWeight: 2,fillColor: waypoints[wp].clr,
fillOpacity: 0.1,map: map,center: waypoints[wp].center,radius:waypoints[wp].sz
};
wpCircle = new google.maps.Circle(wpOptions);
But some of the circles (wpCircle) can cover quite a large area - this stops the mouse move and click events. Is there any way to make the circles invisible to the mouse movement so the mouse responds to the underlying map and ignores the objects that have been laid on?
Many thanks
Kevin
Whoops happened again. Found the answer and was looking too deeply.
clickable:false
for (var wp in waypoints) {
var wpOptions = {strokeColor: waypoints[wp].clr,strokeOpacity: 0.5,strokeWeight: 2,fillColor: waypoints[wp].clr,
fillOpacity: 0.1,map: map,center: waypoints[wp].center,radius:waypoints[wp].sz,clickable: false
};
This is perfect now.
Thanks anyway
Kevin

Image causing click event to fire twice?

I'm creating a Google map with markers and infowindows. I'd like each infowindow to have a zoom button that causes the Google map to zoom in and center and the corresponding marker.
See this jsfiddle for an example. The code for the infowindow is relatively simple:
infoWindowArray[i] = new google.maps.InfoWindow({
position: latlng,
content: "<a href='/"+pitches[i].slug+".html'>"+pitches[i].name + " ("+ pitches[i].club+")</a><br /><img width='16' alt='Loupe' src='//upload.wikimedia.org/wikipedia/commons/thumb/4/48/Loupe.svg/32px-Loupe.svg.png'/> <a id='zoom_"+pitches[i].id+"' href='javascript:void(0)'>Zoom In</a>"
});
However, my script is very temperamental in Chrome, and in Firefox it always fails. The reason is because one click is somehow being fired twice. So this function:
function setupZoomListeners(object, title, google_position, map){
google.maps.event.addListener(object, 'domready', function() {
google.maps.event.addDomListener(document.getElementById("zoom_"+title), 'click', function(){
if (document.getElementById("zoom_"+title).innerHTML == "Zoom In"){
map.setCenter(google_position);
map.setZoom(14);
document.getElementById("zoom_"+title).innerHTML = "Zoom out";
} else {
map.setCenter(new google.maps.LatLng(53.5, -2.2));
map.setZoom(6);
document.getElementById("zoom_"+title).innerHTML = "Zoom In";
}
});
}
is zooming in and out because it hears two clicks, so the net effect is nothing. However, simply removing the img tag in the infowindow appears to solve all.
Is there any earthly reason why the presence of an img tag would cause the script to interpret one click as two? I've seen lots of cases where too many clicks are registered - what generally causes this?
Can anyone advise how to solve this? Thanks.
Its capturing your domready event twice (at least in my version of firefox 19.0.2). You can see that if you start your zoomListeners function like so:
function setupZoomListeners(object, title, google_position, map){
console.log('zoomListener');
google.maps.event.addListener(object, 'domready', function() {
console.log('domReady');
google.maps.event.addDomListener(...
I don't think domready is a good event to be using here. Personally, I would just use one infoWindow object and populate with different HTML when the marker is clicked.
In any case, domready wasn't intended to be used for subsets of the dom as you are doing. I believe it's firing every time you change your markup. So when the lines starting document.getElementById("zoom_"+title).innerHTML = ... are executed the domready event will fire once again.

API V3 - set the zoom -1 from the zoom required to fit the markers

I would like my map to initially zoom out -1 from zoom determined by bounds.
If I do the following, it just zooms in/out, but I want to set the zoom level "a little bigger".
map.fitBounds(bounds);
For the reasons quite well known I can't use map.setZoom afterwards.
Is there some simple way to do this?
The best thing to do is to create an event listener for the map idle event. Once fitbounds has done its thing, the idle event will be triggered.
If you need to do it every time you fit the bounds then create a custom fitbounds method
var fitBounds = function(map, bounds) {
google.maps.event.addListenerOnce(map, 'idle', function() {
this.setZoom(this.getZoom() - 1);
});
map.fitBounds(bounds);
}
Notice we use the addListenerOnce method instead of the normal addListener.
The problem with this method though is you will still see the initial bounds set for a split second before the map zooms out.
You could use an event listener on zoom_changed:
google.maps.event.addListenerOnce(map, 'zoom_changed', function() {
var zoomLevel = map.getZoom();
map.setZoom(zoomLevel-1);
});

How to access Google Maps API v3 marker's DIV and its pixel position?

Instead of Google Maps API's default info window, I'm going to use other jQuery tooltip plugin over marker. So I need to get marker's DIV and its pixel position.
But couldn't get it because there are no id or class for certain marker. Only I can access map canvas div from marker object and undocumented pixelBounds object.
How can I access marker's DIV?
Where can I get DIV's pixel position? Can I convert lat-lng position to pixel values?
==
appended:
I also tried with below code, but it doesn't change when I scroll the map.
var marker = new google.maps.Marker({...});
google.maps.event.addListener(marker, 'click', function() {
var px = this.getMap().getProjection().fromLatLngToPoint(this.getPosition());
console.log("(" + px.x + "," + px.y + ")");
});
I don't really get why would you want to get specific div for marker? If you want to display tooltip then all you need is pixel position of markers anchor (and knowledge about size of marker and placement of anchor), not div element. You can always trigger opening and closing tooltip by hand when event occurs on google.maps side.
For getting pixel position of anchor of given marker you can use this code:
var scale = Math.pow(2, map.getZoom());
var nw = new google.maps.LatLng(
map.getBounds().getNorthEast().lat(),
map.getBounds().getSouthWest().lng()
);
var worldCoordinateNW = map.getProjection().fromLatLngToPoint(nw);
var worldCoordinate = map.getProjection().fromLatLngToPoint(marker.getPosition());
var pixelOffset = new google.maps.Point(
Math.floor((worldCoordinate.x - worldCoordinateNW.x) * scale),
Math.floor((worldCoordinate.y - worldCoordinateNW.y) * scale)
);
In pixelDistance you get offset of specific marker anchor counted from left upper corner of the map (and you can get it's position from map.getDiv() div). Why it works like this (or is there a better way?) you can read in documentation of google maps overlays.
var overlay = new google.maps.OverlayView();
overlay.draw = function() {};
overlay.setMap(map);
var proj = overlay.getProjection();
var pos = marker.getPosition();
var p = proj.fromLatLngToContainerPixel(pos);
You can now access your pixel coordinates through p.x and p.y.
FOLLOWING ADDED POST COMMENT:
The downfall of the overlay projection is that until it your map canvas finishes loading it isn't initialized. I have the following listener that will force whatever method I need to trigger when the map does finish loading.
google.maps.event.addListener(map, 'idle', functionName());
In the mean time I use the following check to avoid any errors before it does draw.
if(overlay.getProjection()) {
// code here
}
One thing to remember when using MBO's code:
When the map tiles are repeated, map.getBounds().getSouthWest() returns "-180" independent of the map's position. A fallback I'm using in this case is calculating the pixel distance to the center instead of the upper left corner, since map.getCenter() seems to return the currently centered point in any case. E.g. (using jQuery):
// Calculate marker position in pixels form upper left corner
var pixelCoordsCenter = map.getProjection().fromLatLngToPoint(map.getCenter());
pixelOffset = new google.maps.Point(
Math.floor((pixelCoordsMarker.x - pixelCoordsCenter.x) * scale + $(container).width()/2),
Math.floor((pixelCoordsMarker.y - pixelCoordsCenter.y) * scale + $(container).height()/2)
);
anyone still looking for an answer to this, have a look here: http://code.google.com/p/google-maps-utility-library-v3/wiki/Libraries
among some other useful google maps stuff there's RichMarker which allows you to add DOM elements of your choice as draggable markers. just add class/id to handle with jQuery.
Rene's answer only gives you the "world coordinates" (that is, coords independent of zoom level and viewport). MBO's answer seems right, though, so that's the one you should accept and vote up (I can't as I just registered) as the solution might easily be overlooked otherwise.
As for an "easier" version, you can use the methods in MapCanvasProjection instead, but that means you'll have to make your own overlay. See here for an example. :P
MapCanvasProjection's fromLatLngToContainerPixel() is probably what the author's after. It will give you the pixel offset relative to the map's container. I did some experiments and found the "simplest" working solution. (I wish Google makes this feature more accessible!)
First you declare a subclass of OverlayView somewhere like so:
function CanvasProjectionOverlay() {}
CanvasProjectionOverlay.prototype = new google.maps.OverlayView();
CanvasProjectionOverlay.prototype.constructor = CanvasProjectionOverlay;
CanvasProjectionOverlay.prototype.onAdd = function(){};
CanvasProjectionOverlay.prototype.draw = function(){};
CanvasProjectionOverlay.prototype.onRemove = function(){};
Then somewhere else in your code where you instantiate the map, you also instantiate this OverlayView and set its map, like so:
var map = new google.maps.Map(document.getElementById('google-map'), mapOptions);
// Add canvas projection overlay so we can use the LatLng to pixel converter
var canvasProjectionOverlay = new CanvasProjectionOverlay();
canvasProjectionOverlay.setMap(map);
Then, whenever you need to use fromLatLngToContainerPixel, you just do this:
canvasProjectionOverlay.getProjection().fromLatLngToContainerPixel(myLatLng);
Note that because the MapCanvasProjection object will only be available once draw() is called, which is sometime before the map's idle, I suggest creating a boolean "mapInitialized" flag, set it to true on the first map idle callback. And then do what you need to do only after that.
Well, if you MUST access the DIV, here's some code. Beware that this will only work with the standard marker (20x34px), and it'll find all markers. You might want to improve this hack to suit your needs...
BEWARE! THIS IS A HACK
var a=document.getElementById('map_canvas');
var b=a.getElementsByTagName('img');
var i, j=b.length;
for (i=0; i<j; i++) {
if(b[i].src.match('marker_sprite.png')){
if(b[i].style.width=='20px' && b[i].style.height=='34px') {
c=b[i].parentElement;
console.log(c.style.top+":"+c.style.left);
// this is a marker's enclosing div
}
}
}
A working snippet jQuery style ready to copy/paste:
step 1 - initialize your map and options
<html>
<head>
<script src="get-your-jQuery" type="text/javascript"></script>
<script src="get-your-maps.API" type="text/javascript"></script>
<script type="text/javascript">
<!--
$(document).ready(function(){
var bucharest = new google.maps.LatLng(44.43552, 26.10250);
var options = {
zoom: 14,
center: bucharest,
mapTypeId: google.maps.MapTypeId.ROADMAP
}
As you can see, lower, the variable map is not preceded by VAR, because it should be Global as we use another function to get the fromLatLngToContainerPixel. For more details check closures.
map = new google.maps.map($("#map_canvas")[0], options);
var marker = new google.maps.Marker({
position: google.maps.LatLng(44.4407,26.0864),
map: map
});
new google.maps.event.addListener(marker, 'mouseover', function(){
placeMarker( marker.getPosition(),'#tooltip');//param1: latlng, param2: container to place result
});
new google.maps.event.addListener(map, 'bounds_changed', function(){
$("#tooltip").css({display:'none'}); //this is just so you can see that all goes well ;)
});
overlay = new google.maps.OverlayView();
overlay.draw = function() {};
overlay.setMap(map);
}); //here ends jQuery.ready
function placeMarker(location, ID){
var containerPixel = overlay.getProjection().fromLatLngToContainerPixel(location);
var divPixel = overlay.getProjection().fromLatLngToDivPixel(location);
$(ID).css({top:containerPixel.y, left:containerPixel.x, 'dislay':'block'});
}
//-->
</script>
</head>
<body>
<div id="tooltip" style="width:100px; height:100px; position:absolute; z-index:1; background:#fff">Here I am!</div>
<div id="map_canvas" style="width:300px; height:300px"></div>
</body>
</html>
I found it's easiest to assign a custom icon and use the img src attribute to get to the element. You can still use the default google maps icon, just save it locally.
$("#map img[src='my_marker.png']").getBoundingClientRect();
For many circumstances the complex math used the calculate and change the pin position in the accepted answer may be appropriate.
For my particular use I just created a transparent PNG with a canvas significantly larger than I needed for the icon. Then I just experimented moving the pin around within the transparent background and applying the new image to the map.
Here is the spec for adding the custom pin image, with examples:
https://developers.google.com/maps/documentation/javascript/examples/icon-simple
This method will definitely scale as an offset in pixels instead of an actual different long/lat even when you zoom in.
Try this way, got div by event.
marker.addListener("click", markerClicked);
function markerClicked(event) {
// here you can get the marker div by event.currentTarget
}