Related
I have a leaflet map that displays the area around where the person currently is (currentlocation), and typically I want the map to 'follow' the person as they travel. I am using a mymap.panTo command for this. This much is working fine. It updates the map every 5 seconds and pans to centre on the current person perfectly.
Occasionally though the person may want to scroll the map further afield to see something. This works ... until the 5 second counter kicks in and pans the map back to their location again. Annoying.
I have seen on various map apps a button/toggle on the map that the person can click on to stop the map following them. In fact usually it turns off if the map is shifted manually and then they'd click the toggle to pan back to their current location. Please see the image attached of a google map highlighting what they call a "Show Your Location" button. That's what I want.
But how is this done? Is this some sort of custom leaflet control that I cannot find? Or is it done fully programmatically somehow (and any sample code snippets?).
any help appreciated.
Below is the bit of code I use to display my map.
var streetmap = L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}', {
attribution: 'Map data © OpenStreetMap contributors, Imagery © Mapbox',
id: 'mapbox/streets-v11',
accessToken: 'token code here' //redacted token
}),
satellite = L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}', {
attribution: 'Map data © OpenStreetMap contributors, Imagery © Mapbox',
id: 'mapbox/satellite-v9',
accessToken: 'token code here' //redacted token });
var baseMaps = {
"Streetmap": streetmap,
"Satellite": satellite
};
var mymap = L.map('mapid', {
center: [latitude,longitude],
zoom: 17,
layers: [streetmap] //default layer
});
var personicon = L.icon({
iconUrl: '/js/personicon.png',
iconSize: [20, 20]
});
var playerLoc = new L.marker([latitude,longitude], {icon: personicon}) // set player location marker as a declared variable but don't put it on the map yet
elsewhere I have code to start the map (once variables are populated) and then keep updating location
const interval = setInterval(function() {
if (is_running) { // we need to know that there is data populated before showing or updating the map with it
if (!map_started) { //start the map only once
L.control.layers(baseMaps).addTo(mymap); //show choice of layer views
L.control.scale().addTo(mymap); //show scale bar
console.log("Create current player marker:",MYID,latitude,longitude);
playerLoc.setLatLng([latitude,longitude]).addTo(mymap).bindPopup(MYID); //update current player marker, and now show it on the map
map_started=true;
}; //start the map only once
updatemap(); // for current player location and circle colour.
}; //update only if is_running
mymap.invalidateSize(); //reset map view
}, 5000); // update map every 5 seconds
function updatemap() { // Update the current player location on map
playerLoc.setLatLng([latitude,longitude]); //update current player marker instead of creating new ones
mymap.panTo([latitude,longitude]); // pan the map to follow the player (TODO: Can we toggle pan mode?)
}; // end updatemap
this code all works fine. the mymap.panTo([latitude,longitude]); line is what needs to be wrapped in a condition "If pan is allowed, then do mymap.panTo([latitude,longitude]);" But surely there's a standard leaflet control or approach to this? I see this thing all the time elsewhere
You need to listen on the movestart event, to detect if the user moves the map. But this event is also triggered from the panTo function, so you need a flag to indicate if movestart is fired by interval or by the user.
var currentAutoMove = false; // needed to check in `movestart` event-listener if moved from interval or by user
var pauseAutoMove = false; // if true -> Stops moving map
var latitude,longitude;
setInterval(()=>{
latitude = playerLoc.getLatLng().lat + 0.001;
longitude = playerLoc.getLatLng().lng + 0.001;
updatemap();
}, 1000)
function updatemap() { // Update the current player location on map
playerLoc.setLatLng([latitude,longitude]);
if(!pauseAutoMove){
currentAutoMove = true; // Set flag, that currently map is moved by interval
map.panTo([latitude,longitude]);
currentAutoMove = false; // Remove flag, that currently map is moved by interval
}
}
map.on('movestart',(e)=>{
console.log(e, currentAutoMove);
if(!currentAutoMove){ // Check if map is moved by interval or by user
pauseAutoMove = true; // set flag, to stop moving map
}
})
// Start auto move again, if button clicked
L.DomEvent.on(document.getElementById('toPos'),'click',(e)=>{
pauseAutoMove = false; // reset flag, to stop moving map -> start moving map
map.panTo([latitude,longitude]);
})
To create a Control / button to start auto move you only need to search in Google there are many examples, else you can use L.easybutton.
Demo: https://jsfiddle.net/falkedesign/8akw3ust/
With much thanks to Falke Design I have used the suggestions in the answer above. My code now looks like this for the button I wanted:
var panbtn = L.easyButton({
states: [{
stateName: 'pauseAutoMove',
icon: 'fa-sign-in fa-lg',
title: 'Centre display at current Player', //Tooltip
onClick: function(btn, map) {
console.log("AutoMoveButton pressed");
panbtn.state('AutoMove');
mymap.panTo([latitude,longitude]);
}
}, {
stateName: 'AutoMove',
icon: 'fa-crosshairs fa-lg',
}]
}).addTo(mymap);
mymap.on("zoomstart", function (e) { currentAutoMove = true }); //Set flag, that currently map is moved by a zoom command
mymap.on("zoomend", function (e) { currentAutoMove = false }); //Remove flag again
mymap.on('movestart',(e)=>{ //Check if map is being moved
if(!currentAutoMove){ //ignore if it was a natural PlayerLoc Auto update
pauseAutoMove = true; //set flag to stop Auto moving map
console.log("Map moved");
panbtn.state('pauseAutoMove'); //change button style to remove crosshairs
}
});
and inside the updatemap function the code:
if(!pauseAutoMove){ //pan the map to follow the player unless it is on pause
currentAutoMove = true; //Set flag, that currently map is moved by a normal PlayerLoc Auto update
mymap.panTo([latitude,longitude]);
currentAutoMove = false; //Remove flag again
};
much thanks.
so I'm making a website using leaflet with dozens of base maps. I want to incorporate information about each map that is only visible if the user wants it. To do this, I would like to make an overlay map with popups, but I want the popups to change depending on the base map selected by the user.
How would I go about doing this?
Thank You So Much
You need to either use a plugin that keeps track of the base maps for you (like active layers) or you need to do it yourself.
If you are using the Leaflet layers control, you can subscribe to the basemapchange event to do this easily.
You need two things: active base layer management (easy) and dynamic popups (not too hard)
To wit:
First, here is the event handler to track active base layer when it changes.
map.on("baselayerchange",
function(e) {
// e.name has the layer name
// e.layer has the layer reference
map.activeBaseLayer = e.layer;
console.log("base map changed to " + e.name);
});
Because using L.marker().bindPopup() creates the popup content right there and does not support callbacks, you must manually create the popups in response to click event by calling map.openPopup() with your dynamic html (dynamic because it uses a variable: the active basemap name)
marker.on("click", function(e) {
var html = "Current base layer: <br/><b>" + map.activeBaseLayer.options.name + "<b>";
map.openPopup(html,
e.latlng, {
offset: L.point(1, -24)
});
});
Here is a working example on JS fiddle: http://jsfiddle.net/4caaznsc/
Working code snippet also below (relies on Leaflet CDN):
// Create the map
var map = L.map('map').setView([39.5, -0.5], 5);
// Set up the OSM layer
var baseLayer1 = L.tileLayer(
'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 18,
name: "Base layer 1"
});
var baseLayer2 = L.tileLayer(
'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 18,
name: "Base layer 2"
});
// add some markers
function createMarker(lat, lng) {
var marker = L.marker([lat, lng]);
marker.on("click", function(e) {
var html = "Current base layer: <br/><b>" + map.activeBaseLayer.options.name + "<b>";
map.openPopup(html,
e.latlng, {
offset: L.point(1, -24)
});
});
return marker;
}
var markers = [createMarker(36.9, -2.45), createMarker(36.9, -2.659), createMarker(36.83711, -2.464459)];
// create group to hold markers, it will be added as an overlay
var overlay = L.featureGroup(markers);
// show overlay by default
overlay.addTo(map);
// show features
map.fitBounds(overlay.getBounds(), {
maxZoom: 11
});
// make up our own property for activeBaseLayer, we will keep track of this when it changes
map.activeBaseLayer = baseLayer1;
baseLayer1.addTo(map);
// create basemaps and overlays collections for the layers control
var baseMaps = {};
baseMaps[baseLayer1.options.name] = baseLayer1;
baseMaps[baseLayer2.options.name] = baseLayer2;
var overlays = {
"Overlay": overlay
};
// create layers control
var layersControl = L.control.layers(baseMaps, overlays).addTo(map);
// update active base layer when changed
map.on("baselayerchange",
function(e) {
// e.name has the name, but it may be handy to have layer reference
map.activeBaseLayer = e.layer;
map.closePopup(); // any open popups will no longer be correct; take easy way out and hide 'em
});
#map {
height: 400px;
}
<script src="https://npmcdn.com/leaflet#0.7.7/dist/leaflet.js"></script>
<link href="https://npmcdn.com/leaflet#0.7.7/dist/leaflet.css" rel="stylesheet"/>
<div id="map"></div>
I use the following block of JavaScript to try to show a WMS layer. I'm using OpenLayers 2.8.
The map's base layer (Openstreetmap) shows correctly, it zooms to the correct area, the "pyramid" layer is shown in the layer switcher, but no request to its WMS service is ever made (so the fact that the URL, styles and params are dummies shouldn't matter -- it never even attempts to get them).
OpenLayers does try to get a WMS layer once I pan or zoom far enough so that the Gulf of Guinea is in view (but all my data is in the Netherlands). This suggests a projection problem (WGS84's (0, 0) point is there), but I don't understand why OpenLayers doesn't even try to fetch a map layer elsewhere. My data is in EPSG:3857 (Web Mercator) projection.
/*global $, OpenLayers */
(function () {
"use strict";
$(function () {
$(".map").each(function () {
var div = $(this);
var data_bounds = div.attr("data-bounds");
console.log("data_bounds: " + data_bounds);
if (data_bounds !== "") {
var map = new OpenLayers.Map(div.attr("id"), {
projection: "EPSG:3857"});
var extent = JSON.parse(data_bounds);
var bounds = new OpenLayers.Bounds(
extent.minx, extent.miny,
extent.maxx, extent.maxy);
map.addLayer(
new OpenLayers.Layer.OSM(
"OpenStreetMap NL",
"http://tile.openstreetmap.nl/tiles/${z}/${x}/${y}.png",
{buffer: 0}));
map.addLayer(
new OpenLayers.Layer.WMS(
"pyramid", "http://rasterserver.local:5000/wms", {
layers: "test",
styles: "test"
}, {
singleTile: true,
isBaseLayer: false,
displayInLayerSwitcher: true,
units: 'm'
}));
map.addControl(new OpenLayers.Control.LayerSwitcher());
map.zoomToExtent(bounds);
}
});
});
})();
Edit: the 'data_bounds' console print prints (with some added formatting):
data_bounds: {
"minx": 582918.5701295201,
"miny": 6923595.841021758,
"maxx": 821926.9006116659,
"maxy": 7079960.166533174
}
It zooms to the correct region in the north of the Netherlands, so I don't think the problem is there.
Since posting, I found out that if I don't use the OSM layer, and instead use the WMS layer as baselayer, it works. So perhaps there's some incompatibility with a OSM baselayer and a WMS layer added to it? But then I don't get that it does seem to do something near WGS84 (0, 0).
I eventually managed to fix this by giving the map an explicit maxExtent:
var extent = JSON.parse(data_bounds);
var bounds = new OpenLayers.Bounds(
extent.minx, extent.miny,
extent.maxx, extent.maxy);
var map = new OpenLayers.Map(div.attr("id"), {
projection: "EPSG:3857",
maxExtent: bounds
});
Oddly enough this doesn't limit the user's ability to pan and zoom around the world, but it does make the overlay work...
I suggested an issue and the author closed it on Github, but I am still left with no conclusion. Longitudes range from -180 to 180. But sometimes, Leaflet returns longitudes like 474.2578215 from getBounds(), which then of course nothing gets returned in my database.
I was told: It's intended behavior. That happens when you zoom too far out and/or drag the map to another copies of the world, and getBounds longitudes are not wrapped by default. You can use LatLng wrap method to get what you want though — e.g. bounds.getSouthWest().wrap().
Ok. So I added the wrap method in there, and the correct data is returned, but now no markers will show up on the map. This is probably due to the marker locations not being within that high number range (what leaflet thinks is the coordinates of the boundaries...)
I'm not sure that zooming or dragging is the cause of the issue. The problem persists when refreshing the page, where the user does no zooming or dragging events have occurred. I have the zoom limited in the init with: minZoom: 6, maxZoom: 13.
I should also note, that this code (unchanged) used to work just fine. Here is my code:
$(document).ready(function(){ initmap(); });
var map;
var plotlist;
var plotlayers=[];
function initmap(){
// set up the map
map = new L.Map('map');
//get our map
var osmUrl='http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
var osmAttrib='© OpenStreetMap contributors';
var osm = new L.TileLayer(osmUrl, {minZoom: 6, maxZoom: 13,
attribution: osmAttrib});
map.setView(new L.LatLng(<?=$slat;?>, <?=$slng;?>),9);
map.attributionControl.setPrefix('');
map.addLayer(osm);
getGameMarkers();
map.on('moveend', onMapMove);
}
function onMapMove(e){
getGameMarkers();
}
function getGameMarkers(){
var center = map.getCenter();
var zoo = map.getZoom();
var bounds = map.getBounds();
console.log(bounds);
var min = bounds.getSouthWest().wrap();
var max = bounds.getNorthEast().wrap();
$.ajax({type: "GET", url: "./ajax/markers.php", dataType: "json", data: "clat="+center.lat+"&clng="+center.lng+"&zoom="+zoo+"&minlat="+min.lat+"&minlng="+min.lng+"&maxlat="+max.lat+"&maxlng="+max.lng+cookiestr,
success: function(data){
if (data.showmap == 1) {
plotlist = data.map_data;
removeMarkers();
for (i=0;i<plotlist.length;i++) {
var iconic = String(plotlist[i].icon);
var plotmark = new L.marker([plotlist[i].lat,plotlist[i].lng], {icon: L.icon({iconUrl: iconic, iconSize: [32, 32]}) }).bindPopup(plotlist[i].html);
map.addLayer(plotmark);
plotlayers.push(plotmark);
}
$("#content").html(data.html);
}else {
$("#map_content").show();
$("#map_content").html(data.main_content);
$("#content").html(data.side_content);
}
}
});
}
The wrap() functions give the correct coordinates, and the DB returns the correct plots. But for some reason, they don't display. Here are the plots returned (the map_data portion):
map_data: [{lat:36.672825, lng:-76.541748, icon:./img/avicon3.png,…},…]
0: {lat:36.672825, lng:-76.541748, icon:./img/avicon3.png,…}
1: {lat:36.901314, lng:-76.041870, icon:./img/avicon2.png,…}
2: {lat:37.101192, lng:-76.264343, icon:./img/avicon3.png,…}
3: {lat:37.300274, lng:-75.673828, icon:./img/avicon3.png,…}
4: {lat:37.348328, lng:-76.830139, icon:./img/avicon3.png,…}
5: {lat:37.2481003, lng:-76.1194000, icon:./img/bicon3.png,…}
6: {lat:37.0298691, lng:-76.3452225, icon:./img/ricon.png,…}
7: {lat:37.6087608, lng:-77.3733063, icon:./img/ricon.png,…}
8: {lat:37.7440300, lng:-77.1316376, icon:./img/ricon.png,…}
9: {lat:37.5917015, lng:-77.4207993, icon:./img/bicon2.png,…}
10: {lat:37.5206985, lng:-77.3783112, icon:./img/ricon.png,…}
11: {lat:37.3306999, lng:-77.3227615, icon:./img/ricon.png,…}
12: {lat:37.1228981, lng:-75.9063034, icon:./img/bicon2.png,…}
No errors in console, and as I said, sometimes it all works (when getBounds doesn't return a crazy big LON). So what in the heck am I doing wrong, and more importantly, how do I solve it?
This is due to worldCopyJump being set to false by default. Setting it to true, and the markers will display correctly, as the world won't overlap.
I was seeing a similar thing, except I was processing a map click event via a callback as event.latlng.lng. Adding worldCopyJump: true did not fix this issue for me - I was still seeing longitude values greater than 180.
In the end, I called the wrap() method to the latlng object:
event.latlng.wrap().lng
which fixed the problem.
I've never experienced those incorrect bounds, but wouldn't programmaticly limiting them serve as at least temporary solution?
As for markers not showing - does it happen when you are moving the map? If so it is quite likely caused by 'moveend' events overlapping. I don't know how many points are there on average but if data download doesn't finish and add markers the next call of 'moveend' will delete all existing markers and start replacing them (also it will override 'plotlist' which migth cause errors later on). You could try adding them to global list after all operations are finished and markers are drawn or you could move only markers not in new set.
On sidenote - why not use geojson to create those layers? With 'pointToLayer' and 'onEachFeature' you could customize markers/popups and have one layer at the end, rather then long list of markers.
'worldCopyJump': true work for me.
Can see this link.
<!DOCTYPE html>
<html lang="en">
Lat: <p id="LatClick"> </p><br>
Lng: <p id="LOnClick" > </p><br>
<head>
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.0.1/dist/leaflet.css" />
<meta name="referrer" content="never">
<style>
body {
padding: 0;
margin: 0;
}
html, body, #map {
height: 80%;
}
.lorem {
font-style: italic;
color: #AAA;
}
</style>
</head>
<body>
<div id="map" ></div>
<script src="https://unpkg.com/leaflet#1.0.1/dist/leaflet.js"></script>
<script>
var map = L.map('map',{'worldCopyJump': true});
map.setView([31.623116,84.497856], 8); L.tileLayer( 'http://{s}.tile.thunderforest.com/cycle/{z}/{x}/{y}.png', { maxZoom: 18}).addTo(map);
//var marker = L.marker([31.2, 97]).addTo(map);
var theMarker={};
map.on('click', function(e){
var coord = e.latlng;
var lat = coord.lat;
map.removeLayer(theMarker);
var lng = coord.lng;
theMarker=L.marker([lat,lng]).addTo(map);
document.getElementById("LatClick").innerHTML = lat;
document.getElementById("LOnClick").innerHTML = lng;
});
</script>
</body>
</html>
I am trying to implement a custom infoBubble that has the box opening to the side of a marker rather than the default position of on top. This has turned out to be harder than expected.
Using the normal infoWindow you can use pixelOffset. See here for the documentation
Using infoBubble this does not seem to be the case. Is there anyway of using pixelOffset in an infoBubble, or something that will do the same thing?
I have found this very difficult to search for, as using a google search such as this returns no relevant results Google Search
Below is all my resources I have been using.
Example of infoBubble here.
My JavaScript to setup the map and infoBubble here.
And now my javascript here just in-case the jsfiddle link is broken.
<script type="text/javascript">
$(document).ready(function () {
init();
});
function init() {
//Setup the map
var googleMapOptions = {
center: new google.maps.LatLng(53.5167, -1.1333),
zoom: 13,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
//Start the map
var map = new google.maps.Map(document.getElementById("map_canvas"),
googleMapOptions);
var marker = new google.maps.Marker({
position: new google.maps.LatLng(53.5267, -1.1333),
title: "Just a test"
});
marker.setMap(map);
infoBubble = new InfoBubble({
map: map,
content: '<div class="phoneytext">Some label</div>',
//position: new google.maps.LatLng(-35, 151),
shadowStyle: 1,
padding: '10px',
//backgroundColor: 'rgb(57,57,57)',
borderRadius: 5,
minWidth: 200,
arrowSize: 10,
borderWidth: 1,
borderColor: '#2c2c2c',
disableAutoPan: true,
hideCloseButton: false,
arrowPosition: 7,
backgroundClassName: 'phoney',
pixelOffset: new google.maps.Size(130, 120),
arrowStyle: 2
});
infoBubble.open(map, marker);
}
</script>
Update
To help with answering this question i have put together a test case here. The important lines are lines 38 & 39, which should specify where to position the label.
Update 2
For the bounty to be awarded i need to see an example of the infoBubble positioned away from its default position above the marker. Preferably to the right hand side of the marker.
Update 3
I have removed the testcase from update 1 because it is hosted on my old company's servers.
This is my solution.
In InfoBubble library
replace
entire InfoBubble.prototype.draw method
with
/*
* Sets InfoBubble Position
* */
InfoBubble.prototype.setBubbleOffset = function(xOffset, yOffset) {
this.bubbleOffsetX = parseInt(xOffset);
this.bubbleOffsetY = parseInt(yOffset);
}
/*
* Gets InfoBubble Position
* */
InfoBubble.prototype.getBubbleOffset = function() {
return {
x: this.bubbleOffsetX || 0,
y: this.bubbleOffsetY || 0
}
}
/**
* Draw the InfoBubble
* Implementing the OverlayView interface
*/
InfoBubble.prototype.draw = function() {
var projection = this.getProjection();
if (!projection) {
// The map projection is not ready yet so do nothing
return;
}
var latLng = /** #type {google.maps.LatLng} */ (this.get('position'));
if (!latLng) {
this.close();
return;
}
var tabHeight = 0;
if (this.activeTab_) {
tabHeight = this.activeTab_.offsetHeight;
}
var anchorHeight = this.getAnchorHeight_();
var arrowSize = this.getArrowSize_();
var arrowPosition = this.getArrowPosition_();
arrowPosition = arrowPosition / 100;
var pos = projection.fromLatLngToDivPixel(latLng);
var width = this.contentContainer_.offsetWidth;
var height = this.bubble_.offsetHeight;
if (!width) {
return;
}
// Adjust for the height of the info bubble
var top = pos.y - (height + arrowSize) + this.getBubbleOffset().y;
if (anchorHeight) {
// If there is an anchor then include the height
top -= anchorHeight;
}
var left = pos.x - (width * arrowPosition) + this.getBubbleOffset().x;
this.bubble_.style['top'] = this.px(top);
this.bubble_.style['left'] = this.px(left);
var shadowStyle = parseInt(this.get('shadowStyle'), 10);
switch (shadowStyle) {
case 1:
// Shadow is behind
this.bubbleShadow_.style['top'] = this.px(top + tabHeight - 1);
this.bubbleShadow_.style['left'] = this.px(left);
this.bubbleShadow_.style['width'] = this.px(width);
this.bubbleShadow_.style['height'] =
this.px(this.contentContainer_.offsetHeight - arrowSize);
break;
case 2:
// Shadow is below
width = width * 0.8;
if (anchorHeight) {
this.bubbleShadow_.style['top'] = this.px(pos.y);
} else {
this.bubbleShadow_.style['top'] = this.px(pos.y + arrowSize);
}
this.bubbleShadow_.style['left'] = this.px(pos.x - width * arrowPosition);
this.bubbleShadow_.style['width'] = this.px(width);
this.bubbleShadow_.style['height'] = this.px(2);
break;
}
};
and then you can use this by
var infoBubble = new InfoBubble({
map: map,
content: "My Content",
position: new google.maps.LatLng(1, 1),
shadowStyle: 1,
padding: 0,
backgroundColor: 'transparent',
borderRadius: 7,
arrowSize: 10,
borderWidth: 1,
borderColor: '#2c2c2c',
disableAutoPan: true,
hideCloseButton: true,
arrowPosition: 50,
backgroundClassName: 'infoBubbleBackground',
arrowStyle: 2
});
Then finally we have to use this method setBubbleOffset(x,y); to set InfoBubble position
infoBubble.setBubbleOffset(0,-32);
It seems as though the infoBubble library itself defaults to positioning the bubble above the marker it is bound to. Take a look at the sample file they included in the library: http://code.google.com/p/google-maps-utility-library-v3/source/browse/trunk/infobubble/examples/example.html . Specifically notice from line 99 to line 122 and the use of the two infobubbles. The first one is bound to the marker, however the second one is a stand-alone and thus if you see line 106, you can define a position to it. Now, based on this understanding I've created an example for you in this fiddle http://jsfiddle.net/pDFc3/. The infoBubble is positioned to the right of the marker.
It's strange, because the infoBubble js library has a function for setPosition ( http://code.google.com/p/google-maps-utility-library-v3/source/browse/trunk/infobubble/src/infobubble.js?r=206 ) see Line 1027. But for some reason after I wait for the DOM to load and try to change the position by going infoBubble.setPosition(newLatLng); I doesn't work. On the contrary, declaring infoBubble.getPosition(); after the DOM loads gives me the current position of the marker the infoBubble is bound to. So setPosition() may have a bug in the js library, because I believe it is still being worked on (I could be wrong maybe it's just buggy).
I've fixed my jsFiddle to solve your issue for when zooming in and out, and positioning the infoBubble accordingly ( http://jsfiddle.net/pDFc3/ ). Let me explain the logic first. Firstly, the maximum zoom level on Google Maps for road map type is 21 - this value is inconsistent for satellite imagery but the maximum zoom the user can go to is 21. From 21, each time you zoom out the differences between two points can be kept consistent "on screen" based on the following logic:
consitent_screen_dist = initial_dist * (2 ^ (21 - zoomlevel))
In our case, the reasonable value for initial distance was 0.00003 degrees (between marker and infoBubble). So, based on this logic I added the following piece to find the initial longitudinal distance between marker and infoBubble:
var newlong = marker.getPosition().lng() + (0.00003 * Math.pow(2, (21 - map.getZoom())));
Likewise, to ensure the distance stays consistent on each zoom level change we simply declare a new longitude as we listen for a change in the zoom level:
google.maps.event.addListener(map, "zoom_changed", function() {
newlong = marker.getPosition().lng() + (0.00003 * Math.pow(2, (21 - map.getZoom())));
infoBubble.setPosition(new google.maps.LatLng(marker.getPosition().lat(), newlong));
});
Keep in mind you can make this code much more efficient by declaring variables for marker.getPosition and other values that are called through methods. So that the method calls aren't repeated and slow your code down.
Unfortunately there is no such option as pixelOffset in InfoBubble. But if you just want to move up Bubble above the marker in your example you should not set map parameter at bubble initialization. Consider the following fiddle (i fixed it for you):
http://jsfiddle.net/ssrP9/5/
P.S. Your fiddle didn't work because you hadnt added resources properly
http://doc.jsfiddle.net/basic/introduction.html#add-resources
I've just come across the exact same issue but couldn't find an answer anywhere. Through a little trial and error I worked it out.
You'll be using the Google JS file for "infoBubble". Go into this file and search for...
InfoBubble.prototype.buildDom_ = function() {
For me, this is on line 203 (but that could be the result of previous shuffling and edits).
Within that function you'll see the position "absolute" declaration. On a new line, you can add marginTop, marginRight, marginBottom and marginLeft. This will nudge the bubble from its default position (which is also dependent on the arrow position declaration in your config)...
This is my code tweak in the bubble JS file which positions the bubble over the top of the marker (due to a design feature)...
var bubble = this.bubble_ = document.createElement('DIV');
bubble.style['position'] = 'absolute';
bubble.style['marginTop'] = this.px(21);
bubble.style['marginLeft'] = this.px(1);
bubble.style['zIndex'] = this.baseZIndex_;
Hope that helps.
In the InfoBubble buildDom function, add:
bubble.className = 'bubble-container';
Now you have a CSS class for each InfoBubble, you can shift it using CSS margin.
You can also use a defined anchor height;
var anchorHeight = YOURNUMBER;
line 874 infobubble.js