Limit Zoom level when markers are on the same position - javascript

Using Leaflet 0.7.3 and last version of markercluster (https://github.com/Leaflet/Leaflet.markercluster), I would like to reproduce a behavior I have been able to do using Google API and associated libraries (Google Marker Cluster V3 & Overlapping Marker Spiderfier V3).
When you have a map with your clusters and you click on one cluster, the API will zoom to bound the markers contained in this cluster. But when all your markers are exactly on the same position, the zoom will be set to its maximum value which is often ugly (no context around the markers or just one street). I would like to limit the zoom level when user click on one cluster with a code like this :
markers.on('clusterclick', function (a) {
map.fitBounds(a.layer.getBounds());
if (map.getZoom() > 14) {
map.setZoom(14);
}
});
With such code the zoom is correctly limited to 14 but the spiderfy is not done (even if I explicitly call a.layer.spiderfy(). Is there something I am missing ?
Here is the fiddle : http://jsfiddle.net/953u41ax/ (click for exemple on 36 then on 12)

Eric, you can achieve this by preventing zoom on click and then manually calling spiderfy on the layer.
Map Settings:
var stationsCluster = new L.MarkerClusterGroup({
maxClusterRadius: 60,
iconCreateFunction: null,
spiderfyOnMaxZoom: true,
showCoverageOnHover: true,
zoomToBoundsOnClick: false
Spidery Listener:
stationsCluster.on('clusterclick', function (a) {
a.layer.spiderfy();
});

You can set the maximum zoom level on the map:
map._layersMaxZoom=8
and then disable the clustering at the same zoom level:
var markers = L.markerClusterGroup({
disableClusteringAtZoom: 8
});
You can play around with this to get the desired effect, this is just an example. JSFiddle is here http://jsfiddle.net/953u41ax/2/

Related

Leaflet geoJson layers hidden outside viewport

I have a leaflet map with a few layers on it.
Whenever layers are not in the viewport, they are hidden untill panning has completed:
Regular view with layers:
Panning right, to show layers outside viewport:
Panning stopped:
As illustrated above, the layers will first become visible once panning has stopped and mouse(finger) released.
I have tried the following, which didn't work
var map = L.map('map',{ bounceAtZoomLimits: false, removeOutsideVisibleBounds: false}).setView([40, 0], 2);
L.geoJson(mapData).addTo(map);
Seems the solution was right in front of me
Adding the following will render the entire map:
var map = new L.Map('map');
map.getRenderer(map).options.padding = 100;
Solution found here

Google Maps JS: pinch to zoom Google Maps without moving marker (ie. Uber & Lyft)

I'm currently building a Phonegap app along with Google's Map JS API.
I have a map that you can drag to select your dropoff location. This is accomplished by collecting the map's center geolocation on the "dragend" event. I then set a pin on top of the map (outside of the JS map) so that you know where the center point is.
Everything works fine until you pinch to zoom and the pin is then displaced. This is due to you zooming and dragging the map at the same time when pinching the screen.
Is there any way to prevent dragging on zoom in order to hold the map at its center point?
Please view the JSfiddle here.
var map;
google.maps.event.addDomListener(window, 'load', onMapReady);
function onMapReady() {
var center = new google.maps.LatLng(30.267153, -97.743061);
var mapOptions = {
center: center,
zoom: 13,
styles: [{"featureType": "poi", "stylers": [{ "visibility": "off" }]},{"featureType": "transit","stylers": [{ "visibility": "off" }]}],
disableDefaultUI: true,
};
map = new google.maps.Map(document.getElementById('map'), mapOptions);
google.maps.event.addListener(map, 'dragend', function() {
center = map.getCenter();
$('#log').html('Lat: ' + center.lat() + ' Lng: ' + center.lng());
});
}
There are two possible solutions for this issue , from what i have read that you have tried using native marker but it is lagging , which itself is another issue that i have a suggestion for :
use crosswalk for building your app , this will extremely boost your app performance , i have experienced it myself , and was really satisified with results ( iam using intel xdk but i believe this will work for phonegap as well ).
use the UX solution : you feel the marker is lagging because after all it is visible !! i would suggest hiding the marker on drag event start , then showin it back on drag event end.
Third solution which makes sense , is using a pinch detector library like this , this , or even solutions mentioned here , and here , pick whatever works best for you , as performance is a point of concern , and previous solution have to be tried , however once you have detected the pinch gesture , you set the map drag to false , set it back again to true after pinch ends .
I dont usually provide much coding in my solution but rather the correct algorithm to help solving the specified issue.
EDIT: ProllyGeek is right, the zoom_changed event fires after the drag has already happened. You can detect if the touch event was near the center of the map (over your marker) and re-center the map after zooming:
google.maps.event.addListener(map, 'dragend', function() {
if (center && center != map.getCenter()) {
map.setCenter(center);
center = null;
}
});
//we should recenter the map if the click/mousedown was within the centerRadius of the center of the map
google.maps.event.addListener(map, 'mousedown', function(clickMouseEvent) {
var mapDiv = document.getElementById('map');
if (pointInCircle(clickMouseEvent.pixel.x,
clickMouseEvent.pixel.y,
mapDiv.offsetWidth/2,
mapDiv.offsetHeight/2,
centerRadius)) {
//map.setOptions({draggable: false}); //disables zoom and dragging
center = map.getCenter(); //save the current center point so we can recenter later.
}
});
//handy function to see if a x,y coordinate is within z radius of another x,y coordinate
//from https://stackoverflow.com/a/16819053/1861459
function pointInCircle(x, y, cx, cy, radius) {
var distancesquared = (x - cx) * (x - cx) + (y - cy) * (y - cy);
return distancesquared <= radius * radius;
}
http://jsfiddle.net/bxfn499f/11/
Original Answer:
Did you try setting draggable to false on the map when the zoom event is fired?
google.maps.event.addListener(map, 'zoom_changed', function() {
map.setOptions({draggable: false});
//heavy handed re-enabling of draggable
//setTimeout(function() { map.setOptions({draggable: true}); }, 5000); //cant drag for five seconds
});
You can programmatically re-enable dragging with the mouseup event (which should fire in lieu of the touchend event) or whatever makes sense in your use case (map.setOptions({draggable: true});). For example:
google.maps.event.addListener(map, 'mouseup', function() {
map.setOptions({draggable: true});
});
http://jsfiddle.net/bxfn499f/6/ I tested from a desktop, so I tweaked the fiddle slightly as the map wasn't loading for me - assuming this was due to the window.load not being fired after $(document).ready(function() { ... }. You'll have to see how this behaves if the drag stars before the zoom event.
I have a solution which might work. Assuming you want the map to center back to the pin.
Add custom control div in your map object. The example is here. Instead of the "center map" div in the example, make your pin the controlling div. Listen to dragend event and set the center at pin's position.
Haven't really tested the solution but seems like it will do the trick.

Google Maps API V3 - KML Layer vs. JS created Polygons

I'm back with more work on the Google Map that I've been working on. Here is the situation:
I have a map of Virginia. It will have markers in it, but I need the markers to be added/removed/editable by several people. As a result, I created a Google map in "My Places" and I'm importing the resulting KML file into a map I'm loading into the API.
I'm also trying to "white out" neighboring states by drawing a polygon over them and adding a white layer over them with 75% opacity, in order to make the state of Virginia stand out more.
However, I've run into a problem where if a marker from the KML layer "overlaps" onto the polygon covering a bordering state, the opaque polygon covers the layer AND the marker becomes unclickable. If one zooms in enough, one can click the marker, but I want people to be able to click the marker from the original zoom.
I've tried making the markers first, then adding the KML, and doing the KML first then drawing the polygons, but it doesn't seem to matter. I even tried a variation of the solution here: Handle when drawing of polygons is complete in google maps api v3 where I put the trigger to add the KML layer inside of the listener event, but still no dice.
My searching on Google also hasn't led me to anything that looks useful. I don't know if this is still a problem with the order the layers are being ordered, or if polygons somehow "override" a KML layer, regardless of the order, or if there is some way to explicitly tell the KML markers to stay on top of the polygons.
First off, here is the main code I'm using to draw the layers right now:
function initialize() {
var mapOptions = {
zoom: 7,
center: new google.maps.LatLng(38, -79.5),
disableDefaultUI: true,
mapTypeId: google.maps.MapTypeId.TERRAIN
}
var map = new google.maps.Map(document.getElementById("map_canvas"), mapOptions);
borderingStates(map);
var participantsLayer = new google.maps.KmlLayer('https://maps.google.com/maps/ms?msa=0&msid=204048902337864904598.0004cc332e8034251c1db&ie=UTF8&ll=37.668046,-80.289717&spn=1.959603,5.642338&output=kml',{preserveViewport:true});
google.maps.event.addListener(map,'idle', function() {
participantsLayer.setMap(map);
});
}
function loadScript() {
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "https://maps.googleapis.com/maps/api/js?key=abc.def&sensor=false&callback=initialize";
document.body.appendChild(script);
}
window.onload = loadScript;
Next, here is an example of the code I'm using to draw the polygons. This happens when I call the borderingStates function above:
//Delaware
DEpoints = [
new google.maps.LatLng(39.7188, -75.7919),
new google.maps.LatLng(39.5210, -75.7837),
...
new google.maps.LatLng(39.8296, -75.6477),
new google.maps.LatLng(39.7199, -75.7906)
];
// Construct the polygon
var Delaware = new google.maps.Polygon({
paths: DEpoints,
fillColor: invisColor,
strokeOpacity: 0,
fillOpacity: .75
});
Delaware.setMap(map);
And then I repeat that for each state I draw a polygon for - I think there are 6 or 7.
I can't tell that there is anything in particular about the code that wouldn't cause it to work other than perhaps how Google Maps inherently treats polygons and KML Layers.
I would suggest making your polygons with KmlLayer (or FusionTablesLayer) as well.
You can control the ordering of layers by the order that you add them to the map (first one is on the bottom). I believe native Google Maps API v3 Polygons will always appear above layers. Your other option would be to make the Polygons "unclickable" (clickable: false) [not sure if this will work or not].
This is my first submission and at the risk of being labelled a cowboy, this was my solution to this problem:
Remove the Polygon on the first click event
Set a timer to reinstate the Polygon (after 1 1/2 seconds)
Capture the click event on the KML element during the 1 1/2 second window of opportunity
google.maps.event.addListener(polygon, 'click', function () {
polygon.setMap(null); // hide polygon for a 1 1/2 seconds
window.setTimeout(function () {
polygon.setMap(map_canvas);
}, 1500);
// process polygon click now
});
This method does mean that you'll process both the Polygon and KML element click events but in our case that was fine.

Place the Google Maps pegman underneath markers

I have a Google V3 map which uses steetView and some map markers.
The little yellow streetView pegman sits on the map on top of the markers.
Is there a way to change the z-indexes so that my markers will be above the pegman
(so that they can be easlly clicked on without having to zoom in)?
In case anything is not clear, here is a fiddle....
http://jsfiddle.net/spiderplant0/BRkCA/
After a bit of experimenting I came up with this...
$("#map_canvas img[src*=cb_scout]").parent("div").css({'zIndex': -200});
$($("#map_canvas img[src*=cb_scout]")[1]).parent("div").parent("div").css({'zIndex': -200});
This forces the pegman to sit beneath the markers but now the pegman is no longer dragable and each time the map is moved etc, the pegman jumps above the markers again.
To keep the pegman under your markers you can watch for the pov_changed event and reset the z-index after a short delay
$google.maps.event.addListener(panorama, 'pov_changed', function() {
var func=function(){
$("#map_canvas img[src*=cb_scout]").parent("div").css({'zIndex': -200});
}
setTimeout(func,1000);
}
});
You will also need to change the depth of the pegman after the maps moves, which can be accomplished with the following snippet
google.maps.event.addListener(map, 'idle', function() {
google.maps.event.trigger(panorama, 'pov_changed');
})
If you want to be able to drag the pegman, you must first place it above the markers by having a toggle button swap the pegman's depth and add an exception to the pov_changed event handler preventing the pagman from dropping depths when the toggle button is active.
Okay, this may be a bit hacky... (and I hope I understood what you were doing)
1) Disable street view control
2) Make another control with a lower zIndex than the marker you have.
3) Update street view control with the position of the fake street view marker.
http://jsfiddle.net/z7Lp8/
You can set the zIndex of the marker above google.maps.Marker.MAX_ZINDEX in order for the pegman to remain under the marker. MAX_ZINDEX is the maximum default z-index that the API will assign to a marker. Marker z-indexes only work when optimizations are turned off on all markers on the map.
Forked fiddle from question to illustrate: http://jsfiddle.net/brendaz/t4v8nhoq/
var marker1 = new google.maps.Marker({
position: new google.maps.LatLng(54.975, -2.020),
map: map,
zIndex: google.maps.Marker.MAX_ZINDEX + 1,
optimized: false
});

Drag (move) a polygon using Google Maps v3

The Google Maps API for a Polygon does not offer a drag method.
What would be an efficient way of implementing such a feature (i.e., sufficiently optimised so that it would not kill a four year old laptop)?
Thank you!
I found the Google Maps V2 Polygon Implementation to be very limiting for the needs I have had and solved it by creating a custom overlay. My group is currently stuck on IE6 so I have yet to migrate over to Google Maps V3 - but taking a quick look at the API shows that you could probably do a similar thing that I did in V2 with V3.
Essentially the idea is:
Create a Custom Overlay
Populate it with your own SVG/VML Polygons and attach a drag event to this custom polygon object
Custom Overlays:
Here is some information to get you started on making your own custom overlay:
http://code.google.com/apis/maps/documentation/javascript/overlays.html#CustomOverlays
Creating your own "Dragable" Polygon Object:
Once you get that down you'll want to add your own polygons to the custom overlay instead of using GPolygons. I went through the painful process of learning SVG/VML and writing a library to bridge SVG/VML together - you could do that, but I would recommend starting by trying to use another library such as Raphaël.
http://raphaeljs.com/
Using Raphaël will save you a whole lot of time trying to figure out how to get cross-browser Vector Graphic (Polygon) functionality - and best of all it supports drag events already, here is an example from their library:
http://raphaeljs.com/graffle.html
Once you have a custom overlay and you are able to throw some Raphaël objects onto it the last step is to translate the coordinates you want from a Lat/Lng value to a Pixel value. This is available in the MapCanvasProjection of V3:
http://code.google.com/apis/maps/documentation/javascript/reference.html#MapCanvasProjection
You can use fromLatLngToDivPixel to figure out what the actual pixel values are for the points on your Raphael polygon, draw it, then add it to the overlay with a drag event.
Since version 3.11 (dated Jan 22, 2013) it's possible to just set the draggable property onto the google.maps.Polygon instance; see this example.
If you want to programmatically move a polygon, you'll need a custom Google Maps Extension which I wrote, as the API does not provide such a method.
Here's how I do it. Find the approximate center of the polygon, and add a marker, then add a drag listener to the marker. On lat/lng change, subtract the difference from the original marker lat/lng, subtract the difference to each of the paths, then, set the original position to the new position. Make sure that in your javascript api call that you have library=geometry,drawing
google.maps.event.addListener(draw, 'overlaycomplete', function(shape) {
// POLYGON
if (shape.type == 'polygon') {
var bounds = new google.maps.LatLngBounds(); var i;
var path = shape.overlay.getPath();
for (i = 0; i < path.length; i++) { bounds.extend(path.getAt(i)); }
shape.latLng = bounds.getCenter();
marker = getMarker(map,shape);
shape.overlay.marker = marker;
markers.push(marker);
}
google.maps.event.addListener(marker, 'drag', function(event) {
shape.overlay.move(event.latLng, shape, path);
});
google.maps.event.addListener(shape.overlay, 'rightclick', function() {
this.setMap(null);
this.marker.setMap(null);
draw.setDrawingMode('polygon');
});
});
}
google.maps.Polygon.prototype.move = function(latLng, shape, p) {
var lat = latLng.lat();
var lng = latLng.lng();
latDiff = shape.latLng.lat()-lat;
lngDiff = shape.latLng.lng()-lng;
for (i = 0; i < p.length; i++) {
pLat = p.getAt(i).lat();
pLng = p.getAt(i).lng();
p.setAt(i,new google.maps.LatLng(pLat-latDiff,pLng-lngDiff));
}
shape.latLng = latLng;
}
function getMarker(map,shape){
var infowindow = new google.maps.InfoWindow();
if(shape.type=='polygon'){ latLng = shape.latLng; }
marker = new google.maps.Marker({
position: latLng,
map:map,
draggable:true,
clickable: true,
animation: google.maps.Animation.DROP
});
shape.overlay.marker = marker;
shape.overlay.bindTo('center',marker,'position');
google.maps.event.addListener(marker, 'click', (function(marker) {
return function() {
infowindow.setContent('polygon');
infowindow.open(map, marker);
toggleBounce(marker);
}
})(marker));
google.maps.event.addListener(infowindow,'closeclick', (function(marker) {
return function() {
marker.setAnimation(null);
}
})(marker));
return marker;
}
If you have any questions, feel free to contact me.
You could have markers for each point on the polygon, these markers could have drag and at the end of each drag, the polygon could be redrawn.
You could also have a marker in the center of the polygon representing the polygon as a whole, when you move that marker, every marker could be moved by the same amount to maintain the shape.
Okay - so after seeing the website you are trying to implement I started to feel like Raphael may not be necessary because it is a pretty heavy JS Library - and if you are only trying to draw a rectangle polygon I thought, why not just do it with a single lightweight DIV instead?
However I think the Raphael solution would still hold water for many other cases - so I think I'll just post another possible answer.
Here is a working example I threw together:
http://www.johnmick.net/drag-div-v3/
Feel free to take a look at the source:
http://www.johnmick.net/drag-div-v3/js/main.js
Essentially we do the following
Create the Custom Overlay
Create the draggable div polygon and, using jQuery UI, make it draggable
Tie an event that listens to when the dragging has stopped that updates the LatLng position of the rectangle
Add the object to the Custom Overlay
Implement the draw function to redraw the rectangle during zooms and pans
Currently I am only storing one LatLng value for the Rectangle (being the top left corner) - you could easily extend this example to store all 4 points of the rectangle and have the shape dynamically resize itself on zooms. You may want to do that, otherwise as users zoom out they will get a climate report for a larger and larger area.

Categories

Resources