I'd like to help the user to input an orientation for a segment with OpenLayers.
I have that form where user can input the bearing for a point, but I would like to help him by :
start drawing the first vertice of a segment on the map when the user clicks on a button, (that first vertice being a known point)
then the user just has to click for the second vertice, and bearing is computed automatically.
See the fiddle here or SO snippet below.
I'm almost done : I can compute the bearing when a segment is drawn. But there's an exception at the very end of the script : I can't get OL to draw automatically the first point of my segment.
Thank you to anyone who can help.
<script src="http://openlayers.org/api/OpenLayers.js"></script>
<body>
<div id="map" style="height: 500px"></div>
</body>
<script>
var CONSTANTS = {
MAP_FROM_PROJECTION: new OpenLayers.Projection("EPSG:4326"), // Transform from WGS 1984
MAP_TO_PROJECTION: new OpenLayers.Projection("EPSG:900913") // to Spherical Mercator Projection
};
function radians(n) {
return n * (Math.PI / 180);
}
function degrees(n) {
return n * (180 / Math.PI);
}
function computeBearing(startLat, startLong, endLat, endLong) {
startLat = radians(startLat);
startLong = radians(startLong);
endLat = radians(endLat);
endLong = radians(endLong);
var dLong = endLong - startLong;
var dPhi = Math.log(Math.tan(endLat / 2.0 + Math.PI / 4.0) / Math.tan(startLat / 2.0 + Math.PI / 4.0));
if (Math.abs(dLong) > Math.PI) {
if (dLong > 0.0) dLong = -(2.0 * Math.PI - dLong);
else dLong = (2.0 * Math.PI + dLong);
}
return (degrees(Math.atan2(dLong, dPhi)) + 360.0) % 360.0;
}
map = new OpenLayers.Map("map");
map.addLayer(new OpenLayers.Layer.OSM());
map.setCenter(new OpenLayers.LonLat(3, 47).transform(CONSTANTS.MAP_FROM_PROJECTION, CONSTANTS.MAP_TO_PROJECTION), 6);
var lineLayer = new OpenLayers.Layer.Vector("Line Layer");
map.addLayers([lineLayer]);
var lineControl = new OpenLayers.Control.DrawFeature(lineLayer, OpenLayers.Handler.Path, {
handlerOptions: {
maxVertices: 2,
freehandMode: function(evt) {
return false;
}
},
featureAdded: function(feature) {
var drawnLinePoints = feature.geometry.getVertices();
var lonlat1 = drawnLinePoints[0].transform(CONSTANTS.MAP_TO_PROJECTION, CONSTANTS.MAP_FROM_PROJECTION);
var lonlat2 = drawnLinePoints[1].transform(CONSTANTS.MAP_TO_PROJECTION, CONSTANTS.MAP_FROM_PROJECTION);
var bearingValue = computeBearing(lonlat1.y, lonlat1.x, lonlat2.y, lonlat2.x);
console.log(bearingValue);
}
});
map.addControl(lineControl);
lineControl.activate();
var handler;
for (var i = 0; i < map.controls.length; i++) {
var control = map.controls[i];
if (control.displayClass === "olControlDrawFeature") {
handler = control.handler;
break;
}
}
// Here I have an exception in the console : I would like
// OL to draw hat point automatically.
handler.addPoint(new OpenLayers.Pixel(50, 50));
</script>
OpenLayers.Handler.Path.addPoint works on OpenLayers.Pixel, not OpenLayers.LonLat:
/**
* Method: addPoint
* Add point to geometry. Send the point index to override
* the behavior of LinearRing that disregards adding duplicate points.
*
* Parameters:
* pixel - {<OpenLayers.Pixel>} The pixel location for the new point.
*/
addPoint: function(pixel) {
this.layer.removeFeatures([this.point]);
var lonlat = this.layer.getLonLatFromViewPortPx(pixel);
this.point = new OpenLayers.Feature.Vector(
new OpenLayers.Geometry.Point(lonlat.lon, lonlat.lat)
);
this.line.geometry.addComponent(
this.point.geometry, this.line.geometry.components.length
);
this.layer.addFeatures([this.point]);
this.callback("point", [this.point.geometry, this.getGeometry()]);
this.callback("modify", [this.point.geometry, this.getSketch()]);
this.drawFeature();
delete this.redoStack;
}
I actually see no good way of achieving this other than adding an addPointByLonLat method:
OpenLayers.Handler.Path.prototype.addPointByLonLat = function(lonLat) {
this.layer.removeFeatures([this.point]);
this.point = new OpenLayers.Feature.Vector(
new OpenLayers.Geometry.Point(lonlat.lon, lonlat.lat)
);
this.line.geometry.addComponent(
this.point.geometry, this.line.geometry.components.length
);
this.layer.addFeatures([this.point]);
this.callback("point", [this.point.geometry, this.getGeometry()]);
this.callback("modify", [this.point.geometry, this.getSketch()]);
this.drawFeature();
delete this.redoStack;
};
Or subclass as your own handler class (propbably cleaner).
Notes:
addPoint is not an API method (so addPointByLonLat is also not). This may result in problem on version changes.
Don't use the compressed/minified JS in development and check docs on methods you use.
Next time consider asking on https://gis.stackexchange.com/.
Consider asking for a code review on your JS.
You can also use insertXY(x,y) function in order to insert a point with geographic coordinates
http://dev.openlayers.org/docs/files/OpenLayers/Handler/Path-js.html#OpenLayers.Handler.Path.insertXY
lonlat = new OpenLayers.LonLat(1,45);
lonlat.transform(CONSTANTS.MAP_FROM_PROJECTION,CONSTANTS.MAP_TO_PROJECTION);
handler.createFeature(new OpenLayers.Pixel(100, 100));
handler.insertXY(lonlat.lon,lonlat.lat);
handler.drawFeature();
You can check it here with a fork of your original jsfiddle : http://jsfiddle.net/mefnpbn2/
See this fiddle for the solution.
tldr :
// draw the first point as I needed, as if a user has clicked on the map
handler.modifyFeature(new OpenLayers.Pixel(50, 50), true);
// draw a first point on the map (not clicked).
// This will be the initial point where the "cursor" is on the map, as long as
// the user hasn't hovered onto the map with its mouse. This make the blue
// line showing current segment to appear, without this segment is drawn but
// no feedback is given to the user as long as he hasn't clicked.
handler.addPoint(new OpenLayers.Pixel(50, 50)); //
Related
I’m coding a solar system model with Three.js. I have a function that calculates the planet position given the day, however I’m not able to draw the correct orbit of the planet (elliptic) starting from a list of points obtained from that function.I googled a lot but I haven’t found an example, how can I do this?
EDIT: these are my functions, one for drawing the ellipse and one for moving the planets
function drawOrbitTest(orbit) {
var material = new THREE.LineBasicMaterial({ color: 0xffffff });
var points = [];
for (var i = 0; i < orbit.orbitPeriod; i += 7) {
//basically what i'm trying to do here is calculating the position of the planet
//with an increment of one week for cycle
if (orbit.orbitPeriod - i <= 7) {
var endPoint = calcPosition(orbit, Date.now() + 86400 * 1000 * orbit.orbitPeriod);
points.push(new THREE.Vector3(endPoint.x * AU, endPoint.y * AU, endPoint.z * AU));
break;
} else {
var middlePoint = calcPosition(orbit, Date.now() + 86400 * 1000 * i);
points.push(new THREE.Vector3(middlePoint.x * AU, middlePoint.y * AU, middlePoint.z * AU));
}
}
var shape = new THREE.Shape(points);
var geomShape = new THREE.ShapeBufferGeometry(shape);
var material = new THREE.LineBasicMaterial(orbit.color);
var ellipse = new THREE.Line(geomShape, material);
scene.add(ellipse);
}
and
function rotateOnEllipse(obj, date) {
var res = calcPosition(obj.orbit, date);
obj.mesh.position.x = res.x * AU;
obj.mesh.position.y = res.y * AU;
obj.mesh.position.z = res.z * AU;
}
EDIT 2: I followed #prisoner849 suggestion, now it works, the planets move on their orbits. However, the orbit now is more like a "ring" than an ellipse; why is it drawn like that?
The issue in the rendering of the line was caused by a bug in the function
calcPosition(orbit,date)
(it was giving back slightly different points if called with date and date+orbitPeriod).
#prisoner849's suggestion worked for me
I've created following map using MapKit.js, with hundred of custom annotations, clustering (yellow dots) and callout popup on annotation click.
What I want to do, when clicking on the popup link, is simply to zoom in one step and center view on the clicked annotation (in a responsive context).
In Google Maps, that I'm used to, you simply position map by it's center and zoom level.
In MapKit.js, you use a center/region combo, and honestly I can't understand how this works.
Official doc is unclear to me, and I wasn't able to find really enlightling ressource.
If someone could explain to me how we are supposed to manage zoom level using center / region combo, it would be really appreciated.
Thanks :-)
[EDIT]
This center/region thing still doesn't make sense to me, so I've decided to override MapKit.js with a zoom feature.
Thanks to this post, I've manage to implement the zoom calculation, which seems to be ok.
I need now to implement the set zoom action.
No success yet, this math things are so far now ^^
Any help is highly welcomed :-)
Function:
function MapKitJsZoom(map) {
var LN2 = 0.6931471805599453; // ???
var WH = 256; // World Height
var WW = 256; // World Width
var MAX = 21; // Max zoom level
// GET CURRENT ZOOM.
var latToRad = function (lat) {
var sin = Math.sin(lat * Math.PI / 180);
var radX2 = Math.log((1 + sin) / (1 - sin)) / 2;
return Math.max(Math.min(radX2, Math.PI), -Math.PI) / 2;
};
var zoom = function (mapPx, worldPx, fraction) {
return (Math.log(mapPx / worldPx / fraction) / LN2);
};
this.get = function () {
var bounds = map.region.toBoundingRegion();
var latFraction = (latToRad(bounds.northLatitude) - latToRad(bounds.southLatitude)) / Math.PI;
var latZoom = zoom(map.element.clientHeight, WH, latFraction);
var lngDiff = bounds.eastLongitude - bounds.westLongitude;
var lngFraction = ((lngDiff < 0) ? (lngDiff + 360) : lngDiff) / 360;
var lngZoom = zoom(map.element.clientWidth, WW, lngFraction);
return Math.round(Math.min(latZoom, lngZoom, MAX));
};
// SET CURRENT ZOOM
this.set = function (zoom) {
// TODO
// I need to calculate latitude and longitude deltas
// that correspond to required zoom based on viewport size
// (map.element.clientWidth and map.element.clientHeight)
map.region.span = new mapkit.CoordinateSpan(latitudeDelta, longitudeDelta);
};
}
Usage:
var map = new mapkit.Map("map");
map.zoom = new MapKitJsZoom(map);
map.addEventListener('region-change-end', function () {
console.log(map.zoom.get());
});
There are two methods of accomplishing this:
1) set center then change zoom level
var newCenter = new mapkit.Coordinate(37.792446, -122.399360);
map._impl.zoomLevel--;
map.setCenterAnimated(newCenter, true);
2) set region using center and span (delta in degress)
var newCenter = new mapkit.Coordinate(37.792446, -122.399360);
var span = new mapkit.CoordinateSpan(.01);
var region = new mapkit.CoordinateRegion(newCenter, span);
map.setRegionAnimated(region)
I am stuck, trying to draw a simple circle buffer using ArcGIS. Here is how I set up my base map:
dojo.require("esri.map");
dojo.require("esri.tasks.servicearea");
dojo.require("dijit.dijit");
dojo.require("dijit.layout.BorderContainer");
dojo.require("dijit.layout.ContentPane");
dojo.require("esri/layers/FeatureLayer");
dojo.require("esri/symbols/SimpleMarkerSymbol");
var mapLayers = new Array();
var map;
var GL = null;
function setMap() {
function init() {
require(
[
"esri/map",
"dojo/dom-construct",
"dojo/domReady!",
"esri/tasks/GeometryService"
],
function
(
Map,
domConstruct
) {
map = Map("map-canvas",
{
//infoWindow: popup
});
map.setZoom(1);
coreFunctions();
});
}
dojo.ready(init);
dojo.connect(map, "onClick", addCircle);
}
function coreFunctions() {
try {
addLayersToMap();
}
catch (err) {
console.log(err);
}
}
function addLayersToData() {
var layer = new esri.layers.ArcGISTiledMapServiceLayer("https://www.onemap.sg/ArcGIS/rest/services/BASEMAP/MapServer");
mapLayers.push(layer);
var layer2 = new esri.layers.ArcGISTiledMapServiceLayer("http://www.onemap.sg/ArcGIS/rest/services/LOT_VIEW/MapServer");
mapLayers.push(layer2);
layer2.hide();
}
function addLayersToMap() {
addLayersToData();
for (var a = 0; a < mapLayers.length; a++) {
map.addLayer(mapLayers[a]);
}
map.setZoom(1);
}
So here is another method where I try to draw a circle graphic on the map:
function addCircle(e) {
sym = new esri.symbol.SimpleFillSymbol().setColor(new dojo.Color([180, 0, 180, 1.0]));
console.log("clicked the map: ", e);
var pt, radius, circle, ring, pts, angle;
pt = e.mapPoint;
circle = new esri.geometry.Polygon(map.spatialReference);
ring = []; // point that make up the circle
pts = 40; // number of points on the circle
angle = 360 / pts; // used to compute points on the circle
for (var i = 1; i <= pts; i++) {
// convert angle to raidans
var radians = i * angle * Math.PI / 180;;
// add point to the circle
ring.push([pt.x + radius * Math.cos(radians), pt.y + radius * Math.sin(radians)]);
console.log(ring[i]);
}
ring.push(ring[0]); // start point needs to == end point
circle.addRing(ring);
map.graphics.add(new esri.Graphic(circle, sym));
console.log("added a graphic");
}
It did execute the function but there is not circle plotted on the map and as well as error message. I wonder which part of my logic went wrong?
Thanks in advance.
At API version 3.8 there is already a class for this task https://developers.arcgis.com/javascript/jsapi/circle-amd.html with this class you can even draw geodesic circles.
You're very, very close - the only problem is you're never setting the value of your variable radius, so all your coordinates are being calculated as pt.x + (nothing) * Math.cos(radians)....which in javascript is NaN. I added radius = 100; before your for loop and everything works nicely.
This is actually a pretty easy error to debug. IMO if you're not using Chrome for ESRI JS development, then you should be - its inbuilt dev tools are excellent for examining object properties. The way I worked out what was going on was simply to dump the map.graphics.graphics array to the console - you can then drill down into the geometry.rings of each array element and very quickly work out that each point has x = NaN and y = NaN, and from there it's obvious that there's something wrong with your calculation.
I'm writing a custom overlay for a Google Map. I have a serious of lat-long points, and I want to images with arrows pointing to and from them. I'm following Google's custom overlay tutorial (https://developers.google.com/maps/documentation/javascript/overlays#CustomOverlays).
In my overlay's "constructor," this line fails:
'this.setMap(this.map_);'
What do I need to do to make it work?
This is my overlay's "constructor." The broken line is at the bottom. All of the calls to "alert" are in there as I'm testing. The last call isn't reached, which is why I think 'this.setMap()' isn't working.
//This serves as a constructor for a link overlay
function LinkOverlay(startNodeCoordinates, endNodeCoordinates, map)
{
alert("constructor start");
this.map_ = map;
//These are the lat-long coordinates of where the link starts and ends
this.startNodeCoordinates_ = startNodeCoordinates;
this.endNodeCoordinates_ = endNodeCoordinates;
alert("constructor coordinates stored");
// We define a property to hold the image's
// div. We'll actually create this div
// upon receipt of the add() method so we'll
// leave it null for now.
this.div_ = null;
alert("constructor div saved");
//We need to know if we draw the arrow up or down and left or right.
//We calculate this by finding the bearing between the two nodes. If
//the bearing is N to NE, then the arrow goes up and to the right,
//for example. If the bearing is E to SE, then the arrow goes down
//and to the right, and so on.
//Calculate bearing
/*
* This algorithm determines the bearing (or angle) between two coordinate points.
* It was adapted from http://www.movable-type.co.uk/scripts/latlong.html
*/
alert("constructor calculating bearing")
this.bearing_ = null;
var lat1 = this.startNodeCoordinates_.lat();
var lat2 = this.endNodeCoordinates_.lat();
var dLon = this.startNodeCoordinates_.lng() - this.endNodeCoordinates_.lng();
var y = Math.sin(dLon) * Math.cos(lat2);
var x = Math.cos(lat1)*Math.sin(lat2) -
Math.sin(lat1)*Math.cos(lat2)*Math.cos(dLon);
this.bearing_ = Math.atan2(y, x);
alert("constructor bearing found (bearing = " + this.bearing_ + ")");
this.arrowUp_ = null;
this.arrowRight_ = null;
this.image_ = null;
alert("constructor picking image");
if((this.bearing_ >= 0 && this.bearing_ < (Math.PI * 0.5)) || this.bearing_ == (Math.PI * 2))
{
alert("constructor NE");
//If bearing is N to NE, the arrow goes up and to the right
this.arrowUp_ = new Boolean(true);
this.arrowRight_ = new Boolean(true);
this.image_ = "../../Content/map_images/link_overlay/up_right_black.png";
}
else if(this.bearing_ >= (Math.PI * 0.5) && this.bearing_ < Math.PI)
{
alert("constructor SE");
//If bearing is E to SE, the arrow goes down and to the right
this.arrowUp_ = new Boolean(false);
this.arrowRight_ = new Boolean(true);
this.image_ = "../../Content/map_images/link_overlay/down_right_black.png";
}
else if(this.bearing_ >= Math.PI && this.bearing_ < (Math.PI * 1.5))
{
alert("constructor SW");
//If bearing is S to SW, the arrow goes down and to the left
this.arrowUp_ = new Boolean(false);
this.arrowRight_ = new Boolean(false);
this.image_ = "../../Content/map_images/link_overlay/down_left_black.png";
}
else
{
alert("constructor NW");
//If bearing is W to NW, the arrow goes up and to the left
this.arrowUp_ = new Boolean(true);
this.arrowRight_ = new Boolean(false);
this.image_ = "../../Content/map_images/link_overlay/up_left_black.png";
}
alert("constructor adding to map");
// Explicitly call setMap() on this overlay
this.setMap(this.map_);
alert("constructor end");
}
//This "subclasses" a link overlay from Google Map's OverlayView class
LinkOverlay.prototype = new google.maps.OverlayView();
This is the code that creates new LinkOverlays (in Razor syntax):
#:var startNodeCoordinates = new google.maps.LatLng('#startNode.Latitude', '#startNode.Longitude');
#:var endNodeCoordinates = new google.maps.LatLng('#endNode.Latitude', '#endNode.Longitude');
#:var routeLine = new LinkOverlay(startNodeCoordinates, endNodeCoordinates, networkMap);
I had the same problem. I fixed it using 'new' to instanciate my custom overlay.
In your case, are you sure that when instanciate your overlay, you use :
linkOverlay = new LinkOverlay(s, e, map)
My map has several hundred markers within a city. Usually no more than a 20 mile radius.
I've read through the documentation and haven't found a way to set the init to automatically pan between every marker, no matter the distance.
The default behavior is to pan if close, jump if far.
I understand why they would do this since the map doesn't load the whole world at the selected zoom level and it could screw up if the distance was too great. However, I think it could handle 20 mile radius with minimal complaints.
If anyone has any ideas, I'd love to hear them.
Thanks
The threshold of the smooth panning does not depend on the distance between the current center and the new target. It depends on whether the change will require a full page scroll (horizontally and vertically) or not:
Quoting from the API Reference:
panTo(latLng:LatLng)
Changes the center of the map to the given LatLng. If the change is less than both the width and height of the map, the transition will be smoothly animated.
Therefore, as long as you are zoomed out such that your viewport is 20 miles in height and width, you should be guaranteed smooth panning for distances under 20 miles.
Here's a solution that pans smoothly and also allows for other click requests to be queue'd up while a previous pan is already in progress:
var panPath = []; // An array of points the current panning action will use
var panQueue = []; // An array of subsequent panTo actions to take
var STEPS = 50; // The number of steps that each panTo action will undergo
function panTo(newLat, newLng) {
if (panPath.length > 0) {
// We are already panning...queue this up for next move
panQueue.push([newLat, newLng]);
} else {
// Lets compute the points we'll use
panPath.push("LAZY SYNCRONIZED LOCK"); // make length non-zero - 'release' this before calling setTimeout
var curLat = map.getCenter().lat();
var curLng = map.getCenter().lng();
var dLat = (newLat - curLat)/STEPS;
var dLng = (newLng - curLng)/STEPS;
for (var i=0; i < STEPS; i++) {
panPath.push([curLat + dLat * i, curLng + dLng * i]);
}
panPath.push([newLat, newLng]);
panPath.shift(); // LAZY SYNCRONIZED LOCK
setTimeout(doPan, 20);
}
}
function doPan() {
var next = panPath.shift();
if (next != null) {
// Continue our current pan action
map.panTo( new google.maps.LatLng(next[0], next[1]));
setTimeout(doPan, 20 );
} else {
// We are finished with this pan - check if there are any queue'd up locations to pan to
var queued = panQueue.shift();
if (queued != null) {
panTo(queued[0], queued[1]);
}
}
}
We developed a workaround to smoothly animate the panTo in all cases.
Basically in cases that the native panTo will not animate smoothly, we zoom out, panTo and zoom in to the destination location.
To use the code below, call smoothlyAnimatePanTo passing the map instance as first parameter and the destination latLng as second parameter.
There is a jsfiddle to demonstrate this solution in action here. Just edit the script tag to put your own google maps javascript api key.
Any comments and contributions will be welcome.
/**
* Handy functions to project lat/lng to pixel
* Extracted from: https://developers.google.com/maps/documentation/javascript/examples/map-coordinates
**/
function project(latLng) {
var TILE_SIZE = 256
var siny = Math.sin(latLng.lat() * Math.PI / 180)
// Truncating to 0.9999 effectively limits latitude to 89.189. This is
// about a third of a tile past the edge of the world tile.
siny = Math.min(Math.max(siny, -0.9999), 0.9999)
return new google.maps.Point(
TILE_SIZE * (0.5 + latLng.lng() / 360),
TILE_SIZE * (0.5 - Math.log((1 + siny) / (1 - siny)) / (4 * Math.PI)))
}
/**
* Handy functions to project lat/lng to pixel
* Extracted from: https://developers.google.com/maps/documentation/javascript/examples/map-coordinates
**/
function getPixel(latLng, zoom) {
var scale = 1 << zoom
var worldCoordinate = project(latLng)
return new google.maps.Point(
Math.floor(worldCoordinate.x * scale),
Math.floor(worldCoordinate.y * scale))
}
/**
* Given a map, return the map dimension (width and height)
* in pixels.
**/
function getMapDimenInPixels(map) {
var zoom = map.getZoom()
var bounds = map.getBounds()
var southWestPixel = getPixel(bounds.getSouthWest(), zoom)
var northEastPixel = getPixel(bounds.getNorthEast(), zoom)
return {
width: Math.abs(southWestPixel.x - northEastPixel.x),
height: Math.abs(southWestPixel.y - northEastPixel.y)
}
}
/**
* Given a map and a destLatLng returns true if calling
* map.panTo(destLatLng) will be smoothly animated or false
* otherwise.
*
* optionalZoomLevel can be optionally be provided and if so
* returns true if map.panTo(destLatLng) would be smoothly animated
* at optionalZoomLevel.
**/
function willAnimatePanTo(map, destLatLng, optionalZoomLevel) {
var dimen = getMapDimenInPixels(map)
var mapCenter = map.getCenter()
optionalZoomLevel = !!optionalZoomLevel ? optionalZoomLevel : map.getZoom()
var destPixel = getPixel(destLatLng, optionalZoomLevel)
var mapPixel = getPixel(mapCenter, optionalZoomLevel)
var diffX = Math.abs(destPixel.x - mapPixel.x)
var diffY = Math.abs(destPixel.y - mapPixel.y)
return diffX < dimen.width && diffY < dimen.height
}
/**
* Returns the optimal zoom value when animating
* the zoom out.
*
* The maximum change will be currentZoom - 3.
* Changing the zoom with a difference greater than
* 3 levels will cause the map to "jump" and not
* smoothly animate.
*
* Unfortunately the magical number "3" was empirically
* determined as we could not find any official docs
* about it.
**/
function getOptimalZoomOut(map, latLng, currentZoom) {
if(willAnimatePanTo(map, latLng, currentZoom - 1)) {
return currentZoom - 1
} else if(willAnimatePanTo(map, latLng, currentZoom - 2)) {
return currentZoom - 2
} else {
return currentZoom - 3
}
}
/**
* Given a map and a destLatLng, smoothly animates the map center to
* destLatLng by zooming out until distance (in pixels) between map center
* and destLatLng are less than map width and height, then panTo to destLatLng
* and finally animate to restore the initial zoom.
*
* optionalAnimationEndCallback can be optionally be provided and if so
* it will be called when the animation ends
**/
function smoothlyAnimatePanToWorkarround(map, destLatLng, optionalAnimationEndCallback) {
var initialZoom = map.getZoom(), listener
function zoomIn() {
if(map.getZoom() < initialZoom) {
map.setZoom(Math.min(map.getZoom() + 3, initialZoom))
} else {
google.maps.event.removeListener(listener)
//here you should (re?)enable only the ui controls that make sense to your app
map.setOptions({draggable: true, zoomControl: true, scrollwheel: true, disableDoubleClickZoom: false})
if(!!optionalAnimationEndCallback) {
optionalAnimationEndCallback()
}
}
}
function zoomOut() {
if(willAnimatePanTo(map, destLatLng)) {
google.maps.event.removeListener(listener)
listener = google.maps.event.addListener(map, 'idle', zoomIn)
map.panTo(destLatLng)
} else {
map.setZoom(getOptimalZoomOut(map, destLatLng, map.getZoom()))
}
}
//here you should disable all the ui controls that your app uses
map.setOptions({draggable: false, zoomControl: false, scrollwheel: false, disableDoubleClickZoom: true})
map.setZoom(getOptimalZoomOut(map, destLatLng, initialZoom))
listener = google.maps.event.addListener(map, 'idle', zoomOut)
}
function smoothlyAnimatePanTo(map, destLatLng) {
if(willAnimatePanTo(map, destLatLng)) {
map.panTo(destLatLng)
} else {
smoothlyAnimatePanToWorkarround(map, destLatLng)
}
}
See this other SO answer about using javascript's setInterval function to create a periodic function that calls panBy on your map: Can Google Maps be set to a slow constant pan? Like a globe revolution?
This can be used to pan the map by x pixels on each call to panBy, allowing you to slow down the panBy rate (since you are only telling gmaps to panTo a short distance).
As Daniel has mentioned, the built-in panTo() function will not work for you if the two points are too far apart. You can manually animate it yourself if that's the case though: for each zoom level, figure out the distance covered by say 100 pixels. Now, when you have to pan to a point, you can use this information to figure out if the panTo() funciton will animate or jump. If the distance moved is so big that it will not animate, you should do the animation manually - compute some intermediate waypoints between your current map center and your destination, and pan to them in sequence.
#tato.rodrigo
I don't have enough reputation to post as an answer so am posting as a reply to Tato here as his plugin works well for me and is exactly what I needed but has a bug (I use it as a dependency so the map variable is passed through the function)
You need to pass map to function getOptimalZoomOut(latLng, currentZoom) {}
as you use the map variable inside that function.
like this: function getOptimalZoomOut(latLng, currentZoom, map) {}
and later: map.setZoom(getOptimalZoomOut(destLatLng, initialZoom)); pass it in: map.setZoom(getOptimalZoomOut(destLatLng, initialZoom, map)); and maybe another stray one.