Preventing Google Maps Move After Displaying Infowindow - javascript

I'm trying to display a infowindow on a Google Maps. It displays perfect, when you hover over a marker it loads up a infowindow but the map jumps to fit in the window. I don't want the map to move but rather infowindow set its position according to map. Booking.com has something like this.
EDIT: Added my code
Here is the stripped down version of my code. I'm getting all the info from an AJAX service and this service returns response (which holds some more info too).
$.ajax({
url: 'URL',
dataType: "json",
type: "GET",
success: function(response) {
// delete all markers
clearOverlays();
var infowindow = new google.maps.InfoWindow();
for (var i = 0; i < response.length; i++) {
item = response[i];
var marker = new google.maps.Marker({
position: new google.maps.LatLng(item.lat, item.lng),
map: map,
url: item.detail_url
});
markersArray.push(marker);
// display infowindow
google.maps.event.addListener(marker, "mouseover", (function(marker, item) {
return function() {
infowindow.setOptions({
content: 'SOME CONTENT HERE FOR INFOWINDOW'
});
infowindow.open(map, marker);
}
})(marker, item));
// remove infowindow
google.maps.event.addListener(marker, 'mouseout', function() {
infowindow.close();
});
// marker click
google.maps.event.addListener(marker, 'click', function() {
window.location.href = marker.url;
});
}
}
});
As you can see below, the first image shows the infowindow displayed at bottom of marker while second one shows the infowindow displayed on top of the marker. The map doesn't move but infowindow sets its position within the boundries of the map.

In your declaration for setting the info window options infowindow.setOptions, add the following option to disable the panning of the map when the infowindow is displayed, disableAutoPan : true.
However this will also mean you'll need to calculate the real estate you have available to display the infowindow on your map and give it a position using the position option. Otherwise it won't be guaranteed that your infowindow will be completely visible on the map.
https://developers.google.com/maps/documentation/javascript/reference#InfoWindowOptions
EDIT: How to calculate screen real estate
I realize I was pretty vague with my suggestion to calculate the screen real estate available to set the position of your infowindow, when part of your question was to actually set the infowindow's position. So I've provided an update to my answer on how you can calculate the screen real estate and adjust your infowindow's position.
The key is to first convert your points from LatLng to pixels, and find out the pixel coordinate of the marker on the map with relation to the pixel coordinate of the map's center. The following snippet demonstrates how this can be done.
getPixelFromLatLng: function (latLng) {
var projection = this.map.getProjection();
//refer to the google.maps.Projection object in the Maps API reference
var point = projection.fromLatLngToPoint(latLng);
return point;
}
Once you've got your pixel coordinates, you'll need to find out which quadrant of the map canvas the marker is currently residing in. This is achieved by comparing the X and Y coordinates of the marker to the map, like so:
quadrant += (point.y > center.y) ? "b" : "t";
quadrant += (point.x < center.x) ? "l" : "r";
Here, I'm determining if the point is in the bottom right, bottom left, top right or top left quadrant of the map based on it's relative position to the map's center. Keep in mind this is why we use pixels as opposed to LatLng values, because LatLng values will always yield the same result - but reality is the marker can be in any quadrant of the map canvas depending on panning.
Once you know which quadrant the marker is in, you can give offset values to the infowindow to position it so it's visible on the map - like so:
if (quadrant == "tr") {
offset = new google.maps.Size(-70, 185);
} else if (quadrant == "tl") {
offset = new google.maps.Size(70, 185);
} else if (quadrant == "br") {
offset = new google.maps.Size(-70, 20);
} else if (quadrant == "bl") {
offset = new google.maps.Size(70, 20);
}
//these values are subject to change based on map canvas size, infowindow size
Once the offset value is determined you can just adjust the pixelOffset of your infowindow on whatever listener you have invoking the infoWindow.open() method. This is done by using the setOptions() method for infowindows:
infowindow.setOptions({pixelOffset : self.getInfowindowOffset(self.map, marker)});
Here is a working JSFiddle example of the solution described above.
Note: You will notice the annoying "arrow" on the infowindow displaying desbite the position of your infowindow, this is part of the default Google Map's setting for infowindows and I could not find a proper way of getting rid of it. There was a suggestion here, but I couldn't get it to work. Alternatively you can use the infobox library or the infobubble library - which gives you more styling options.

Suvi Vignarajah's answer is great, what I didn't like about it was how unpredictable the position of the window is near the edges.
I tweaked it so that the bubble glues to the wall as it should be expected: http://jsfiddle.net/z5NaG/5/
The only condition is that you need to know the width & height of the infoWindow, you can position it quite nicely. You would work it out through the maps' pixels. If you don't know it you could initialize the InfoWindow offscreen, get it's size and proceed opening it again at the right spot. Ugly but would work.
First initialize on overlay at time of map initialization by running this function:
ourOverlay:null,
createOverlay:function(){
this.ourOverlay = new google.maps.OverlayView();
this.ourOverlay.draw = function() {};
this.ourOverlay.setMap(this.map);
},
Then at open event adjust the offset like so:
getInfowindowOffset: function (map, marker) {
// Settings
var iwWidth = 240; // InfoWindow width
var iwHeight = 190; // InfoWindow Height
var xOffset = 0;
var yOffset = 0;
// Our point of interest
var location = this.ourOverlay.getProjection().fromLatLngToContainerPixel(marker.getPosition());
// Get Edges of map in pixels: Sout West corner and North East corner
var swp = this.ourOverlay.getProjection().fromLatLngToContainerPixel(map.getBounds().getSouthWest());
var nep = this.ourOverlay.getProjection().fromLatLngToContainerPixel(map.getBounds().getNorthEast());
// Horizontal Adjustment
if(location.x<iwWidth/2){
xOffset= iwWidth/2-location.x;
}else if(location.x>nep.x-iwWidth/2){
xOffset = (nep.x-iwWidth/2)-location.x ;
}
// Vertical Adjustment
if(location.y<iwHeight){
yOffset = location.y + iwHeight-(location.y-nep.y);
}
// Return it
return new google.maps.Size(xOffset, yOffset);
},
And the result is pretty nice, but this triangle seems out of place now.
You can use InfoBox to remove the pointy arrow on bottom:
GmapsManager.infowindow = new InfoBox({
content:'',
alignBottom: true,
closeBoxURL: "",
});
and style the bubble with the following code:
.infobox-popup{
background: #fff;
-webkit-border-radius: 3px;
border-radius: 3px;
padding: 5px;
margin-left: -100px;
margin-bottom: 30px;
width: 200px;
-webkit-box-shadow: 0 0 1px 0 #595959;
box-shadow: 0 0 1px 0 #595959;
}

Related

How to get the exact position of each div created using Overlay Map Types

I am using Overlay Map Types to create a Grid.
I have already managed to make the dimension of each square (of the grid) correspond to ~2km2 of area when the zoom is equal to 15.
But now I need to know in which square (of the grid) a particular marker (latlng) is.
The problem is that the information obtained does not include the bounds value.
getTile(coord, zoom, ownerDocument) {
It would be necessary to know the bounds of each square.
For example: each time the user clicked on the square, he got bounds of that square on the map.
Is there an easy way to get this information using javascript?
Thank you very much.
EDIT
As #MrUpsidown suggested, I'll add to my question a "minimal reproducible example", which is actually a copy of what's in the link above.
/*
* This demo illustrates the coordinate system used to display map tiles in the
* API.
*
* Tiles in Google Maps are numbered from the same origin as that for
* pixels. For Google's implementation of the Mercator projection, the origin
* tile is always at the northwest corner of the map, with x values increasing
* from west to east and y values increasing from north to south.
*
* Try panning and zooming the map to see how the coordinates change.
*/
class CoordMapType {
tileSize;
constructor(tileSize) {
this.tileSize = tileSize;
}
getTile(coord, zoom, ownerDocument) {
const div = ownerDocument.createElement("div");
div.innerHTML = String(coord);
div.style.width = this.tileSize.width + "px";
div.style.height = this.tileSize.height + "px";
div.style.fontSize = "10";
div.style.borderStyle = "solid";
div.style.borderWidth = "1px";
div.style.borderColor = "#AAAAAA";
return div;
}
releaseTile(tile) {}
}
function initMap() {
const map = new google.maps.Map(document.getElementById("map"), {
zoom: 10,
center: { lat: 41.85, lng: -87.65 },
});
// Insert this overlay map type as the first overlay map type at
// position 0. Note that all overlay map types appear on top of
// their parent base map.
map.overlayMapTypes.insertAt(
0,
new CoordMapType(new google.maps.Size(256, 256))
);
}
In the example above, how can I get the exact position of each div created.
In order to obtain the coordinates (latlng) of each of the vertices.
And, in this case, the ideal would be that, each time the user clicks on one of the divs, he gets this corresponding information.
It's possible?
Thank you very much.

Marker icon displays both the traditional icon and custom icon after setting icon in marker constructor

I have a map on a view which displays some markers and with info boxes.
Everything works fine, but when I set the icon property of the map to a unique image, the marker shows both images with my unique icon overlaying the default google pin.
This is what it currently looks like:
!https://imgur.com/a/6P1tgE7
I have tried numerous definitions on the map constructor but the result has been the same.
var image = 'http://maps.google.com/mapfiles/ms/icons/rangerstation.png';
//Add markers to view
function addMarker(x, y, ward, community, lganame, projecttype) {
var location = new google.maps.LatLng(x, y);
var marker = new google.maps.Marker({
position: location,
map: map,
title: ward
});
marker.setIcon(image);
addInfoWindowToMarker(marker, ward, community, lganame, projecttype);
}
I would like the icon being displayed to be just the one I specify and completely ignore the default Google Maps marker.
I solved the problem by changing my marker definition as such.
Note I also scaled the size down or better presentation.
function addMarker(x, y, ward, community, lganame, projecttype) {
var location = new google.maps.LatLng(x, y);
var marker = new google.maps.Marker({
position: location,
map: map,
title: ward,
//icon: 'http://maps.google.com/mapfiles/ms/icons/rangerstation.png',
icon: new google.maps.MarkerImage(
'http://maps.google.com/mapfiles/ms/icons/rangerstation.png',
null, /* size is determined at runtime */
null, /* origin is 0,0 */
null, /* anchor is bottom center of the scaled image */
new google.maps.Size(20, 20)
)
});
//Call info windows and bounce event listener so the marker animates when it is clicked
addInfoWindowToMarker(marker, ward, community, lganame, projecttype);
}
It works just fine now.

Google Maps infobox position next to marker not over the marker

I want to know how I can position the infobox for the Google markers, not over the pin but something similar to this website. Here, when you hover over group of markers the infobox is shown at the same point thus the mouseout event is not triggered because the mouse is still over the group of markers.
I want to know how I can achieve something similar with the post I did yesterday. So to wrap up the post, is there any styling code which I can apply so I can show the infobox right over the marker?
It is set at 140px in right by default, so set the values for x and y respectively and the box will appear there everytime.
offset values you can arrange yourself.
try this code..
var infowindows = []; //make it global
var popup = new InfoBox({
// size: new google.maps.Size(420,130),
content:content_info
,pixelOffset: new google.maps.Size(10, -100) //these are the offset values to be adjusted accordingly..
,disableAutoPan: false
,closeBoxMargin: "5px 5px 2px 2px"
,boxStyle: {opacity: 0.95
}
});
infowindows.push(popup);
google.maps.event.addListener(marker, 'mouseover', function() {
close_popups();
map.panTo(mycenter1);
popup.open(map, marker);
// currentPopup = null;
});
function close_popups(){
for(var i = 0; i<infowindows.length; i++){
infowindows[i].close();
}
}
You can use the alignBottom and pixelOffset properties of InfoBox to achieve what you want. I created a fiddle which admittedly looks terrible, but I think it can get you on the right track in terms of the implementation. You can polish it to your liking after you get it working :) FIDDLE
Basically, you specify the options like:
var ibOptions = {
content: 'your content',
pixelOffset: new google.maps.Size(-5, -15),
alignBottom: true
};
You can adjust the pixelOffset to your liking, so the content shows over the marker and the mouseout is not triggered.
Then you do this to bind to the mouseover event (which you probably already know):
google.maps.event.addListener(marker, "mouseover", function (e) {
ib.open(map, this);
});

How to display newer Google Map InfoWindows on top of older ones

I am having Google Map with InfoWindows being added dynamically. The issue is, when two InfoWindows overlap, the recent one won't always be on top of older ones.
How can I make sure latter InfoWindow always show up on top of all other InfoWIndows using Javascript/jQuery?
The InfoWindows are added when I receive new images and coordinates through websocket. Here is my code:
function addToMap(image) {
console.log("In addToMap...");
coordinates = image[2].split(',');
pin=new google.maps.LatLng(coordinates[0], coordinates[1]);
if(marker) {
marker.setMap(null);
}
var markerIcon = image_car_icon;
marker=new google.maps.Marker({
position:pin,
zIndexProcess: function( m )
{
return 9999999 + pin_count;
},
icon:markerIcon
});
marker.setMap(map);
map.setCenter(marker.getPosition());
pin_count++;
if(image[0] != "NOP") {
popup = new google.maps.InfoWindow({
content:'<image id="pin_' + pin_count + '" src="data:image/png;base64,' + image[1] +'"/>',
});
popup.open(map, marker);
}
}
popup is the InfoWindow created when I get a new image. Suppose two images have almost same coordinates, I want the second InfoWindow to be on top (z-index). But, most of the time, the first window stays on top.
I have found out a solution. I am manually modifying the z-index of the marker by using the image as the handle. marker_count is a running marker, so the z-index will be greater than the previous marker.
google.maps.event.addListener(popup, 'domready', function() {
console.log("DOM READY...........................");
// Bring latest Image to top
e = $('#pin_' + img_count).parent().parent().parent().parent();
e.css({
'z-index' : (99999 + marker_count),
});
});

Google Maps: Render marker above markerclusterer

I have a set of markers that get clustered on my map.
Another set of markers are displayed individually, and i happen to need these to be displayed above the clusters.
I have tried setting zIndex in the clusters options object, lower than that of the 2nd set of markers, but to no avail.
Any idea how to go about this?
It can be done, but it's a pretty bumpy way until you get there... As Rick says, the problem is that the MarkerClusterer adds an own OverlayView with its cluster icons on a higher pane as the regular markers. The only way to add a marker above the clusters is to beat the clusterer with his own weapons and add an own OverlayView and add the marker icon markup to an even higher pane (read about panes here). I don't really like it - but it's the only way I found.
To do this you have to create a custom overlay implementing google.maps.OverlayView (reference), a good example can be found here (with explanations, I used a bit of code from it).
Here is a rough CustomOverlay prototype:
// build custom overlay class which implements google.maps.OverlayView
function CustomOverlay(map, latlon, icon, title) {
this.latlon_ = latlon;
this.icon_ = icon;
this.title_ = title;
this.markerLayer = jQuery('<div />').addClass('overlay');
this.setMap(map);
};
CustomOverlay.prototype = new google.maps.OverlayView;
CustomOverlay.prototype.onAdd = function() {
var $pane = jQuery(this.getPanes().floatPane); // Pane 6, one higher than the marker clusterer
$pane.append(this.markerLayer);
};
CustomOverlay.prototype.onRemove = function(){
this.markerLayer.remove();
};
CustomOverlay.prototype.draw = function() {
var projection = this.getProjection();
var fragment = document.createDocumentFragment();
this.markerLayer.empty(); // Empty any previous rendered markers
var location = projection.fromLatLngToDivPixel(this.latlon_);
var $point = jQuery('<div class="map-point" title="'+this.title_+'" style="'
+'width:32px; height:32px; '
+'left:'+location.x+'px; top:'+location.y+'px; '
+'position:absolute; cursor:pointer; '
+'">'
+'<img src="'+this.icon_+'" style="position: absolute; top: -16px; left: -16px" />'
+'</div>');
fragment.appendChild($point.get(0));
this.markerLayer.append(fragment);
};
This overlay gets the map, a LatLng object and the URL of an icon. The good thing is that you can write your own HTML to the layer, the bad thing is that you have to handle all the stuff the Maps API does for you (like marker image anchor handling) by your own. The example only works good with 32x32px images where the anchor is in the middle of the image, so it's still pretty rough.
To use the CustomOverlay, just instantiate it like this:
// your map center / marker LatLng
var myLatlng = new google.maps.LatLng(24.247471, 89.920990);
// instantiate map
var map = new google.maps.Map(
document.getElementById("map-canvas"),
{zoom: 4, center: myLatlng, mapTypeId: google.maps.MapTypeId.ROADMAP}
);
// create the clusterer, but of course with markers
//var markerClusterer = new MarkerClusterer(map, []);
// add custom overlay to map
var customCustomOverlay = new CustomOverlay(map, myLatlng, 'http://www.foo.bar/icon.png');
I hope this works for you.
I had the same issue but didn't want to handle a new overlay.
Without having to create a specific Overlay, you can just switch the overlay's parent containers z-indexes.
This can be achieved by using the following function :
_changeOverlayOrder = function(map) {
var panes = map.getPanes();
var markerOverlayDiv = panes.overlayImage.parentNode;
var clusterOverlayDiv = panes.overlayMouseTarget.parentNode;
// Make the clusters clickable.
if(!markerOverlayDiv.style.pointerEvents) {
markerOverlayDiv.style.cssText += ";pointer-events: none;";
}
// Switch z-indexes
if(markerOverlayDiv.style.zIndex < clusterOverlayDiv.style.zIndex) {
var tmp = markerOverlayDiv.style.zIndex;
markerOverlayDiv.style.zIndex = clusterOverlayDiv.style.zIndex;
clusterOverlayDiv.style.zIndex = tmp;
}
};
Hope it helps.
As far as I know this can't be done. The clusters reside in higher pane than the marker image.
https://developers.google.com/maps/documentation/javascript/reference#MapPanes
You can override it with CSS.
CSS
.gm-style-pbc + div > div > div:first-child{ z-index: 108 !important; }
SCSS
.gm-style-pbc{
+ div{
> div{
>div{
&:first-child{
z-index: 108 !important;
}
}
}
}
}

Categories