Google maps V3 custom marker images and fitBounds() - javascript

I am trying to get my custom markers to show up on my map after i have used the fitBounds() method to fit the boundaries of the map to the markers themselves.
I have done this by looping over the markers array and then extending the boundaries of the map to incorporate the marker co-ordinates.
This works fine with the stock google maps markers. However, my client wants to use quite large (36px by 57px) marker images for their site. How do i compensate for this when fitting the boundaries of the map?
At the moment when using the custom marker images they do not all fit inside the boundaries set.

Since you already have calculated the bounds, you may just need to extend the bounds to add enough buffer area to include the large images. The formula you can use to calculate or extend a bounds this way is called a convex hull; the Computational Geometry Algorithms Library has a section on 2D Convex Hull Algorithms or there is a JavaScript Quickhull Article that also includes a nifty online example near the bottom of the page. Hope this is helpful -

The cheap answer is to always zoom out one level after fitBounds(). But we can do a bit better.
I like writing hacks. Here I am making the assumption that the size of your marker will never be larger than 36x57. I tested a while back to find that fitBounds() leaves a margin of around 42 px between the edge and the closest marker (maybe not on mobiles), and I'm also assuming you are not repositioning the marker, that is, it will always be displayed above the given coordinate position. If icons run off to the other sides, adjustments are needed.
My hack takes advantage of a function that measures the pixel position of a LatLng (using the container version, I read here that the div version is not reliable with bounds changes).
Since we know the height of the icon, and where the topmost marker is, we can pan the map south a bit if it's determined to be offscreen. In case there's not enough margin below, the only option is to zoom out. My only concern is it will be jerky because it calls for two events: fitBounds and the custom panning/zooming. The only answer then would be to rewrite a custom fitBounds. When I tested manually the events ran smoothly.
http://jsfiddle.net/sZJjY/
Click to add cat icons, right-click to trigger the resize/panning.
Example: place 3-4 kitties, right-click, then purposely place another that goes off the top, right-click again.
function fitIcons() {
var left = 180.0;
var right = -180.0;
var top = -90.0;
var bottom = 90.0;
for (var i = 0; i < markers.length; i++) {
curlat = markers[i].getPosition().lat();
curlng = markers[i].getPosition().lng();
if(curlat > top) { top = curlat; }
if(curlat < bottom) { bottom = curlat; }
if(curlng > right) { right = curlng; }
if(curlng < left) { left = curlng; }
}
var overlay = new google.maps.OverlayView();
overlay.draw = function() {};
overlay.setMap(map);
map.fitBounds(new google.maps.LatLngBounds(
new google.maps.LatLng(bottom, left),
new google.maps.LatLng(top, right)));
topPixels = overlay.getProjection().fromLatLngToContainerPixel(
new google.maps.LatLng(top, right));
bottomPixels = overlay.getProjection().fromLatLngToContainerPixel(
new google.maps.LatLng(bottom, left));
topGap = topPixels.y;
bottomGap = $("#map_canvas").height() - bottomPixels.y;
if(topGap < iconHeight) {
if(bottomGap > iconHeight) {
map.panBy(0, topGap);
}
else {
map.setZoom(map.getZoom() - 1);
}
}
}

Related

Align non-geographical leaflet map maxBounds to the bottom of the container

I am creating a non-geographical map with leaflet, which shows a ski resort.
JSFiddle: https://jsfiddle.net/exophunk/ruzgeqL4/
I am using map.fitBounds(bounds) to fit the map to the container and map.setMaxBounds(bounds) to make sure you can't pan outside the map.
This works perfect when zooming in, but as long as the map is smaller than the viewport height, I would like to "align" the whole thing to the bottom of the container, so the map never moves away from the bottom of the container. As it is a mountain, it is pretty obvious.
I achieved this by adding an offset to the maxBounds, as you can see in this example (blue box = max bounds):
https://jsfiddle.net/exophunk/05cq3rzt/
The problem with this approach is, that you can now pan into the "empty sky" when zooming in, while I would actually like to keep the maxBounds as in the first example, so it also restricts movement upwards.
I think this would mean I would need to resize the max bounds while zooming in maybe? But there, I messed up coordinate systems and containers and wasn't able to do this properly.
How can I make sure the map always sticks to the container bottom while keeping the original max bounds, if possible?
You can overwrite the default wheel zoom and check if the zoom mousepoint is in the bounds, else zoom to the center of the bounds/map:
map.scrollWheelZoom._performZoomOrg = map.scrollWheelZoom._performZoom;
map.scrollWheelZoom._performZoom = function(e){
var mouse = map.scrollWheelZoom._lastMousePos;
var llpixel = map.containerPointToLatLng(mouse)
if(!bounds.contains(llpixel)){
map.scrollWheelZoom._lastMousePos = map.unproject([container.clientWidth/2, mapHeight]);
}
map.scrollWheelZoom._performZoomOrg();
}
https://jsfiddle.net/falkedesign/c04ngftj/

Mapbox: Zooming Out One Level After Using fitBounds Re-Centers Map

I am using Mapbox GL JS and created a dynamic store locator based on their store locator demo. I am using fitBounds to include all store markers on the initial display of the map.
The problem is, it zooms in too close and the map needs a bit of padding. So, I'm trying to back out the zoom one level. The problem is, when I get the zoom level and back out one, the map is recentered to the original center of the map.
I have included code below where I try to reset the center to the fitBounds center but it's not working. Any idea what I'm doing wrong?
var bounds = new mapboxgl.LngLatBounds();
$(markers).each(function() {
var features = $(this);
bounds.extend(features[0].geometry.coordinates);
});
map.fitBounds(bounds);
var mapLat = map.getBounds().getCenter().lat;
var mapLong = map.getBounds().getCenter().long;
map.setCenter = (mapLat, mapLong);
var mapZoom = map.getZoom();
newZoom = mapZoom - 1;
map.setZoom(newZoom);
If all you want to achieve is to add padding to the bounds, mapbox-gl provides an option for this in fitBounds:
map.fitBounds(bounds, {padding: 100}) // adds 100px padding to the bounds
Here is the documentation: https://www.mapbox.com/mapbox-gl-js/api#map#fitbounds

OverlappingMarkerSpiderfier offset length for markers

Background
I use google maps together with clustermarkererplus to display various markertypes with infobubbles on a map. To deal with markers with exactly the same coordinate (or very close) an offset is generated for the markers that overlap by using OverlappingMarkerSpiderfier (OMS). Searching for an answer to this has so far only led me to adjustment of the thickness of the offset lines.
Problem
When there are many markers the offset given by the OMS is too small.
Questions
How can I increase the offset (leg length)?
Can it be dependent on if the number of markers separated by the OMS is more than a certain value?
Example (to show the effect)
Visualisation of what I want to do, increase offset (leg length)
Code
Documentation of OMS is available at: https://github.com/jawj/OverlappingMarkerSpiderfier
//Options I use
options = {keepSpiderfied:true};
//Creation of the OMS
new OverlappingMarkerSpiderfier(map, options);
//Click listener
oms.addListener('click', function(marker, event) {
//Code for showing infowindows
});
//Adding marker
oms.addMarker(marker);
OverlappingMarkerSpiderfier has adjustable offset (leg length) by specifying two different options:
circleFootSeparation: "default:23" (Offset from the center of the circle)
spiralLengthFactor:"default:4" (Value proportional to the offset from the spiral center)
Angular separation can also be adjusted for the spiral
spiralFootSeparation:"default:26"
I'm not familiar with OverlappingMarkerSpiderfier, so just did some research and found this tutorial. It also include 'legWeight' which determine the thickness of the lines joining spiderfied markers to their original locations.
Here's the link for Overlapping Marker Spiderfier: https://github.com/jawj/OverlappingMarkerSpiderfier
I hope it might help you.

Custom Map Types: repeating maps and markers. How to adding padding to map?

With the Google Maps API (v3) I've created a custom map type for a fictional game world. By default, maps, even custom map types, repeat horizontally (see image below).
Larger Image here
Is it possible to keep the map from repeating horizontally? For my map, it does not represent a planet or spherical world, so having it repeat horizontally forever doesn't make sense at all. I have figured out how to simply not load tiles for the repeated maps on the left and right like so:
Larger Image here
HOWEVER, when you create markers, the markers still show up for all the repeated maps:
Larger Image here
Is it possible to keep the markers from repeating? Or is it possible to keep the map from repeating at all? That way I don't have to deal with markers repeating?
Work Around: Limit Panning beyond the Map Bounds
I've read various work-arounds that discuss simply limiting how far the user can pan to the left or right. This won't work for me because I have to allow the user to zoom all the way out and view the entire map at once. If they zoom all the way out, repeated markers are still visible, which is unacceptable.
Is it possible to adding a bunch of padding to the map? That way there is a large amount of space between the maps:
Larger Image here
If I was able to add enough padding, then limiting the panning would work for me, because any repeated markers could be pushed far enough away by the padding that the user would never see them.
Finally my code, pretty simple:
(note: the map tile images I'm using are not available online yet)
<!DOCTYPE html>
<html style='height: 100%'>
<head>
<link rel="stylesheet" type="text/css" href="normalize.css" />
<style>
html, body { height: 100%;}
#map_canvas { height: 1000px;}
</style>
</head>
<body style='height: 100%'>
<div id="map_canvas"></div>
<script src="https://maps.googleapis.com/maps/api/js?sensor=false"></script>
<script type='text/javascript'>
var options =
{
getTileUrl: function(coord, zoom)
{
// Don't load tiles for repeated maps
var tileRange = 1 << zoom;
if ( coord.y < 0 || coord.y >= tileRange || coord.x < 0 || coord.x >= tileRange )
return null;
// Load the tile for the requested coordinate
var file = 'images/zoom' + zoom + '/tile_' + zoom + '_' + (coord.x) + '_' + (coord.y) + '.jpg';
return file;
},
tileSize: new google.maps.Size(256, 256),
minZoom: 1,
maxZoom: 9,
radius: 1738000, // I got this from an example in the api, I have no idea what this does
name: 'Map',
};
var mapOptions =
{
center: new google.maps.LatLng(0,0),
zoom: 2,
backgroundColor: '#000',
streetViewControl: false,
mapTypeControl: false
};
var map = new google.maps.Map(document.getElementById('map_canvas'),mapOptions);
var mapType = new google.maps.ImageMapType(options);
map.mapTypes.set('map', mapType);
map.setMapTypeId('map');
var marker = new google.maps.Marker({
position: new google.maps.LatLng(0,0),
map: map,
title: "Test"
});
</script>
</body>
</html>
In answer to the question: Is it possible to keep the markers from repeating?
Yes.
From Google Maps JavaScript API V3 Reference (3.19), if you set the markerOptions property optimized to false for your marker, it does not repeat but only shows up on the center map.
See: https://developers.google.com/maps/documentation/javascript/reference#MarkerOptions
So, in your code, I would modify var marker as such (adding optimized: false):
var marker = new google.maps.Marker({
position: new google.maps.LatLng(0,0),
map: map,
title: "Test"
optimized: false
});
According to Google's docs (I've added the bolding),
Optimization renders many markers as a single static element. Optimized rendering is enabled by default. Disable optimized rendering for animated GIFs or PNGs, or when each marker must be rendered as a separate DOM element (advanced usage only).
I set optimized to false and then looked through the page to find the id (or at least class) associated with my markers. I was going to make the "extra" markers non-visible. It turns out the elements are there but have no id or class. Just as I was contemplating other ways to identify them using jQuery, I happened to look up at my "map" and realized the "extra" markers were gone! ☺
A word of caution: based on Google's docs, I suspect this behavior (the "extra" markers not showing up) may be an unintended "feature".
Cheers,
Bruce.
Looks to me like you just need to change your starting zoom and min zoom limit.
Even google runs into repeats when you are at zoom level 1, but it doesn't let you zoom out lower than that.
Just add minZoom and maxZoom properties to your options object to limit the zooming.
You can try to put a mask on the repeated area but I didn't tried it. This looks like it can solve your problem: Apply mask to Google Map.
Update: Apply the mask only when you need it, i.e. when the zoom is the lowest. I don't think resizeing the browser window will affect anything in the map. It's also has nothing to do with the question and the problem is if the mask lays on top of the marker.
Update 2: It's seems to be possible with v2 but not with v3. In v2 you can disable horizontal copies in the projection class: Google Maps API v3 with custom map image - markers repeating horizontally.
For those who has still this problem, have a look at my solution.
1- Set the maps zoom to (2) and add marker positions (lat,long) i.e
var minZoomLevel = 2;
map.setZoom(minZoomLevel);
var bounds = new google.maps.LatLngBounds();
for (var i = 0; i < result.length; i++){
var latlng = new google.maps.LatLng(result[i].Lat, result[i].Lng);
bounds.extend(latlng);
});
2- Attach a event listener on zoom changed i.e
google.maps.event.addListener(map, 'zoom_changed', function() {
if (map.getZoom() < minZoomLevel) map.setZoom(minZoomLevel);
});
3- Attach a center changed listener (This done the trick) i.e
google.maps.event.addListener(map, 'center_changed', function()
{
checkBounds(bounds);
}
function checkBounds(allowedBounds) {
if(allowedBounds.contains(map.getCenter())) {
return;
}
var mapCenter = map.getCenter();
var X = mapCenter.lng();
var Y = mapCenter.lat();
var AmaxX = allowedBounds.getNorthEast().lng();
var AmaxY = allowedBounds.getNorthEast().lat();
var AminX = allowedBounds.getSouthWest().lng();
var AminY = allowedBounds.getSouthWest().lat();
if (X < AminX) {X = AminX;}
if (X > AmaxX) {X = AmaxX;}
if (Y < AminY) {Y = AminY;}
if (Y > AmaxY) {Y = AmaxY;}
map.setCenter(new google.maps.LatLng(Y,X));
}
Every time you change the center, it will check your points and restrict map to certain area . Setting zoom will show only one world tile, and check bound will restrict the horizontal scrolling !

Positioning custom markers on a Google Map

I have the following Google Map test app: http://dev.driz.co.uk/googlemap/
As you can see I use geolocation to show your (the user) position on the map, and then some JSON data to populate the map with other markers.
Note: depending where you are in the world you may not see the pins (they are in the UK near Huddersfield) if you zoom out you should seem them.
I am having the following issues:
1.) All the markers have the same titles, so I'm presuming that somewhere in the for loop at the bottom of the page I have made a mistake... Not sure what though?
Fixed in answers below.
2.) The markers have various overlapping issues due to the z-index and also because some of the markers have the same co-ordinates. Is it possible to make it so that markers offset themselves a couple pixels per loop so that they don't overlap, and the z-index automatically increases per loop so they are higher than the previous marker
Need to make it so that when a user hovers the marker it has a higher z-index to make it sit on top... If that makes sense? So in the hover event I need to get the latest offset and then add to that to make it the highest! But how do I alter the zindex of the marker on the hover?
3.) The final thing (and probably the most difficult) is that the tooltips are not equally positioned when moved to the right side of the marker when the map is moved. Any ideas to improve this? They get even worse with the JSON-based markers and slip off the map.
Can anyone help me out with these problems?
Thanks
I don't know if this will work, but its following the pattern of that link I shared, perhaps something like this....
function doToolTip(item) {
return function () {
mTooltip = new Tooltip('<span class="name">' + item.User.Profile.firstname + ' asked:</span> <span class="title">' + item.Post.title + '</span>');
mTooltip.open(this.getMap(), this);
};
}
...and this is your main code. I think 'item needs' initialising outside the scope of the loop (but I could be wrong)
//other code etc...
var item;
for (var i = 0; i < data.length; i++) {
item = data[i];
//other code etc....
google.maps.event.addListener(marker, 'mouseover', doToolTip(item));
//other code etc...
}
OK. I'm guessing here, as I haven't got a local copy of the code, but, It looks like you need to change the z-index when you do the draw function...
//code fragment...
// need to force redraw otherwise it will decide to draw after we show the Tooltip
$(this).css('z-index', 9999);
this.draw();
// show tooltip
With regard to the position of the tooltip, you're going to have to experiment with the draw function, as it seems to calculate the position from the marker. It might be better to work out the position not from the google map coordinates but from the actual position on the page - I think the culprits are:
pos = this.getProjection().fromLatLngToDivPixel(this.get('position'));
// top offset
top = pos.y - this.getAnchorHeight() / 2 - this.wdiv.outerHeight() / 2;
// left offset
if (this.getMap().getCenter().lng() > this.get('position').lng()) {
left = pos.x + this.wdiv.outerWidth();
} else {
left = pos.x - this.wdiv.outerWidth();
}
// window position
this.wdiv.css('top', top);
this.wdiv.css('left', left);
If the positioning is consistently off, you could just apply a correction to the top and left values, if it's more complicated, you'll have to change the algorithm.

Categories