Get center in Cesium Map - gis

I need to know what is the current center of the Cesium Map.
I tried to use viewer.camera.position but it always gives the same z value (x: 16921255.101297915, y: 5578093.302269477, z: 12756274) and I'm not sure about the x and y values. Are they in meters?
Thanks a lot!
EDIT: Solution
With all the help I got (thanks!) I put this together:
getPosition(){
if (viewer.scene.mode == 3) {
var windowPosition = new Cesium.Cartesian2(viewer.container.clientWidth / 2, viewer.container.clientHeight / 2);
var pickRay = viewer.scene.camera.getPickRay(windowPosition);
var pickPosition = viewer.scene.globe.pick(pickRay, viewer.scene);
var pickPositionCartographic = viewer.scene.globe.ellipsoid.cartesianToCartographic(pickPosition);
console.log(pickPositionCartographic.longitude * (180 / Math.PI));
console.log(pickPositionCartographic.latitude * (180 / Math.PI));
} else if (viewer.scene.mode == 2) {
var camPos = viewer.camera.positionCartographic;
console.log(camPos.longitude * (180 / Math.PI));
console.log(camPos.latitude * (180 / Math.PI));
}
};
This function gives longitude/latitude coordinates in degrees.

viewer.camera.position gives you the position at which camera is located in X,Y,Z coordinates in meters in relation to earth center.
Depending on which scene mode you are using approach is different:
SCENE3D:
In order to see at what is the camera looking at you need to get the intersect point of camera's pick ray and map.
function getMapCenter() {
var windowPosition = new Cesium.Cartesian2(viewer.container.clientWidth / 2, viewer.container.clientHeight / 2);
var pickRay = viewer.scene.camera.getPickRay(windowPosition);
var pickPosition = viewer.scene.globe.pick(pickRay, viewer.scene);
var pickPositionCartographic = viewer.scene.globe.ellipsoid.cartesianToCartographic(pickPosition);
console.log(pickPositionCartographic.longitude * (180/Math.PI));
console.log(pickPositionCartographic.latitude * (180/Math.PI));
}
Based on this thread.
Also try to check if camera is looking at the map, and not a the sky.
SCENE2D:
This is a simple 2D view with camera pointing directly down.
From docs:
2D mode. The map is viewed top-down with an orthographic projection
var camPos = viewer.camera.positionCartographic;
console.log(camPos.longitude * (180/Math.PI));
console.log(camPos.latitude * (180/Math.PI));
Remaining case is 2.5D or COLUMBUS_VIEW

Related

Calculate compass heading from DeviceOrientation Event API

For an augmented reality web app for smartphones I'm trying to calculate the compass heading when the user is holding the device in their hand, with the screen in a vertical plane and the top of the screen pointing upwards.
I have taken the suggested formula from http://dev.w3.org/geo/api/spec-source-orientation (see Worked Example) and implemented the following function:
function compassHeading(alpha, beta, gamma) {
var a1, a2, b1, b2;
if ( beta !== 0 || gamma !== 0 ) {
a1 = -Math.cos(alpha) * Math.sin(gamma);
a2 = Math.sin(alpha) * Math.sin(beta) * Math.cos(gamma);
b1 = -Math.sin(alpha) * Math.sin(gamma);
b2 = Math.cos(alpha) * Math.sin(beta) * Math.cos(gamma);
return Math.atan((a1 - a2) / (b1 + b2)).toDeg();
}
else {
return 0;
}
}
while .toDeg() is a Number object extension courtesy http://www.movable-type.co.uk/scripts/latlong.html
/** Converts radians to numeric (signed) degrees */
if (typeof Number.prototype.toDeg == 'undefined') {
Number.prototype.toDeg = function() {
return this * 180 / Math.PI;
};
}
However, the problem is that the calculated compass heading value jumps from about -75 to 80 even if the device (Google Galaxy Nexus) is mounted to hold a static position. This seems to happen in both Google Chrome BETA and FF BETA 23.
Does somebody see an error in my approach or know a more reliable way to calculate the compass heading?
The steps you need to determine the compass heading according to the worked example provided in the specification* are as follows:
Convert returned DeviceOrientation alpha, beta and gamma values from degrees to radians as alphaRad, betaRad, gammaRad.
Compute rotationA (rA) and rotationB (rB) components per the worked example in the specification using alphaRad, betaRad and gammaRad (as shown in the example code below).
Compute compassHeading = Math.atan(rA / rB).
Convert returned half unit circle headings to whole unit circle headings in the range [0-360) degrees.
Convert compassHeading from radians back to degrees (optional).
Here is the worked example from the specification implemented in JavaScript:
function compassHeading(alpha, beta, gamma) {
// Convert degrees to radians
var alphaRad = alpha * (Math.PI / 180);
var betaRad = beta * (Math.PI / 180);
var gammaRad = gamma * (Math.PI / 180);
// Calculate equation components
var cA = Math.cos(alphaRad);
var sA = Math.sin(alphaRad);
var cB = Math.cos(betaRad);
var sB = Math.sin(betaRad);
var cG = Math.cos(gammaRad);
var sG = Math.sin(gammaRad);
// Calculate A, B, C rotation components
var rA = - cA * sG - sA * sB * cG;
var rB = - sA * sG + cA * sB * cG;
var rC = - cB * cG;
// Calculate compass heading
var compassHeading = Math.atan(rA / rB);
// Convert from half unit circle to whole unit circle
if(rB < 0) {
compassHeading += Math.PI;
}else if(rA < 0) {
compassHeading += 2 * Math.PI;
}
// Convert radians to degrees
compassHeading *= 180 / Math.PI;
return compassHeading;
}
window.addEventListener('deviceorientation', function(evt) {
var heading = null;
if(evt.absolute === true && evt.alpha !== null) {
heading = compassHeading(evt.alpha, evt.beta, evt.gamma);
}
// Do something with 'heading'...
}, false);
You can also view a demo of the code provided above.
As of the time of writing (17th Feb 2014) this currently works in:
Google Chrome for Android
Opera Mobile for Android
Firefox Beta for Android
Other browsers do not yet conform to the DeviceOrientation calibration described in the DeviceOrientation Event specification and/or do not provide absolute DeviceOrientation data values making it impossible to determine compassHeading with non-complete data.
* Determining the compass heading of the horizontal component of a vector which is orthogonal to the device's screen and pointing out of the back of the screen.
I have also played some with the DeviceOrientationEvent (might adopt you formula...) and seen similar problems from time to time. You might wanna try a calibration as in this video. You could search youtube for more examples.
It seems that getting compass heading from device orientation events is deprecated, perhaps for privacy reasons. You should look at the Geolocation API, which requires permissions. The events have Coordinates.heading.

Displaying markers within a Bing maps Polygon

I am using Bing maps to display markers on the map.
Now I am adding a functionality where I allow the user to draw a circle around any marker of his choice and let him specify the radius of the circle in kilometres.
I want the circle(polygon) to contain markers within the latitude/longitude bounds of that circle and the markers outside of the circle to disappear.
How do I achieve this?
You have provided no code.
However, I was involved in a similar project so I have an idea what you are talking about.
SInce you are looking for custom polygon you can have a look at this link.
Another way to do it would be to the x,y coordinates of the marker and using Javascript to draw a circle around the map.
THIS JAVASCRIPT SHOULD HELP
function drawCircle(radius, origin) {
var RadPerDeg = Math.PI / 180;
var earthRadius = 3959;
var lat = origin.latitude * RadPerDeg;
var lon = origin.longitude * RadPerDeg;
var locs = new Array();
var AngDist = parseFloat(radius) / earthRadius;
for (x = 0; x <= 360; x++) { //making a 360-sided polygon
var pLatitude, pLongitude;
// With a nice, flat earth we could just say p2.Longitude = lon * sin(brng) and p2.Latitude = lat * cos(brng)
// But it ain't, so we can't. See http://www.movable-type.co.uk/scripts/latlong.html
brng = x * RadPerDeg;
pLatitude = Math.asin(Math.sin(lat) * Math.cos(AngDist) + Math.cos(lat) * Math.sin(AngDist) * Math.cos(brng)); //still in radians
pLongitude = lon + Math.atan2(Math.sin(brng) * Math.sin(AngDist) * Math.cos(lat), Math.cos(AngDist) - Math.sin(lat) * Math.sin(pLatitude));
pLatitude = pLatitude / RadPerDeg;
pLongitude = pLongitude / RadPerDeg;
locs.push(new MM.Location(pLatitude, pLongitude));
};
circle = new MM.Polyline(locs, { visible: true, strokeThickness: 2, strokeDashArray: "1", strokeColor: new MM.Color(200, 0, 0, 200, 0) });
map.entities.push(circle);
};

Get longitude and latitude from the Globe in WebGL

I'm using WebGL globe from http://workshop.chromeexperiments.com/globe/. If any point of the globe is clicked, I need to get the longitude and latitude of that point. These parameters are to be passed to the Google Maps for 2D map.
How can I get the long. and lat. from the webgl globe?
Through this function I'm getting the double clicked point, and through this point I'm finding the long. and lat. But the results are not correct. It seems that the clicked point is not determined properly.
function onDoubleClick(event) {
event.preventDefault();
var vector = new THREE.Vector3(
( event.clientX / window.innerWidth ) * 2 - 1,
-( event.clientY / window.innerHeight ) * 2 + 1,
0.5
);
projector.unprojectVector(vector, camera);
var ray = new THREE.Ray(camera.position, vector.subSelf(camera.position).normalize());
var intersects = ray.intersectObject(globe3d);
if (intersects.length > 0) {
object = intersects[ 0 ];
console.log(object);
r = object.object.boundRadius;
x = object.point.x;
y = object.point.y;
z = object.point.z;
console.log(Math.sqrt(x * x + y * y + z * z));
lat = 90 - (Math.acos(y / r)) * 180 / Math.PI;
lon = ((270 + (Math.atan2(x, z)) * 180 / Math.PI) % 360) - 180;
console.log(lat);
console.log(lon);
}
}
Get the WebGL Globe here https://github.com/dataarts/webgl-globe/archive/master.zip
You can open it directly on Mozilla, if you open it in Chrome it works with earth surface image lack because of Cross-Origin Resource Sharing policy. It needs to be put in a virtual host.
Try to use the function in this way
function onDoubleClick(event) {
event.preventDefault();
var canvas = renderer.domElement;
var vector = new THREE.Vector3( ( (event.offsetX) / canvas.width ) * 2 - 1, - ( (event.offsetY) / canvas.height) * 2 + 1,
0.5 );
projector.unprojectVector( vector, camera );
var ray = new THREE.Ray(camera.position, vector.subSelf(camera.position).normalize());
var intersects = ray.intersectObject(globe3d);
if (intersects.length > 0) {
object = intersects[0];
r = object.object.boundRadius;
x = object.point.x;
y = object.point.y;
z = object.point.z;
lat = 90 - (Math.acos(y / r)) * 180 / Math.PI;
lon = ((270 + (Math.atan2(x, z)) * 180 / Math.PI) % 360) - 180;
lat = Math.round(lat * 100000) / 100000;
lon = Math.round(lon * 100000) / 100000;
window.location.href = 'gmaps?lat='+lat+'&lon='+lon;
}
}
I used the code you share with a little correction and it works great.
The way to let it work correctly is to understand exactly what you pass to the new THREE.Vector3.
This function need three parameters (x, y, z)
z in our/your case is sculpted as 0.5 and it's ok
x and y must be a number among -1 and 1, so, to obtain this values you need to catch the click coordinates on your canvas and then, with this formula, reduce them to a value in this range (-1...0...1);
var vectorX = ((p_coord_X / canvas.width ) * 2 - 1);
var vectorY = -((p_coord_Y / canvas.height ) * 2 - 1);
where p_coord_X and p_coord_Y are the coordinates of the click (referred to the left top corner of your canvas) and canvas is the canvas area where lives your webgl globe.
The problem is how to get the click X and Y coordinates in pixel, because it depends by how your canvas is placed in your HTML enviroment.
For my cases the solution over proposed where not suitable cause i returned always false results; so i build a solution to get extacly the x and y coordinates of my canvas area as i clicked on it (i had for my case too to insert a scrollY page correction).
Now imagine to devide in 4 square your canvas area, a click in the NW quadrant will return for example a -0.8, -05 x and y values, a click in SE i.e. a couple of 0.6, 0.4 values.
The ray.intersectObject() function that follows uses then our click-vector-converted data to understand if our click intersects the globe, if it matches, return correctly the coordinates lat and lon.

Google Maps V3 - How to calculate the zoom level for a given bounds

I'm looking for a way to calculate the zoom level for a given bounds using the Google Maps V3 API, similar to getBoundsZoomLevel() in the V2 API.
Here is what I want to do:
// These are exact bounds previously captured from the map object
var sw = new google.maps.LatLng(42.763479, -84.338918);
var ne = new google.maps.LatLng(42.679488, -84.524313);
var bounds = new google.maps.LatLngBounds(sw, ne);
var zoom = // do some magic to calculate the zoom level
// Set the map to these exact bounds
map.setCenter(bounds.getCenter());
map.setZoom(zoom);
// NOTE: fitBounds() will not work
Unfortunately, I can't use the fitBounds() method for my particular use case. It works well for fitting markers on the map, but it does not work well for setting exact bounds. Here is an example of why I can't use the fitBounds() method.
map.fitBounds(map.getBounds()); // not what you expect
Thanks to Giles Gardam for his answer, but it addresses only longitude and not latitude. A complete solution should calculate the zoom level needed for latitude and the zoom level needed for longitude, and then take the smaller (further out) of the two.
Here is a function that uses both latitude and longitude:
function getBoundsZoomLevel(bounds, mapDim) {
var WORLD_DIM = { height: 256, width: 256 };
var ZOOM_MAX = 21;
function latRad(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;
}
function zoom(mapPx, worldPx, fraction) {
return Math.floor(Math.log(mapPx / worldPx / fraction) / Math.LN2);
}
var ne = bounds.getNorthEast();
var sw = bounds.getSouthWest();
var latFraction = (latRad(ne.lat()) - latRad(sw.lat())) / Math.PI;
var lngDiff = ne.lng() - sw.lng();
var lngFraction = ((lngDiff < 0) ? (lngDiff + 360) : lngDiff) / 360;
var latZoom = zoom(mapDim.height, WORLD_DIM.height, latFraction);
var lngZoom = zoom(mapDim.width, WORLD_DIM.width, lngFraction);
return Math.min(latZoom, lngZoom, ZOOM_MAX);
}
Demo on jsfiddle
Parameters:
The "bounds" parameter value should be a google.maps.LatLngBounds object.
The "mapDim" parameter value should be an object with "height" and "width" properties that represent the height and width of the DOM element that displays the map. You may want to decrease these values if you want to ensure padding. That is, you may not want map markers within the bounds to be too close to the edge of the map.
If you are using the jQuery library, the mapDim value can be obtained as follows:
var $mapDiv = $('#mapElementId');
var mapDim = { height: $mapDiv.height(), width: $mapDiv.width() };
If you are using the Prototype library, the mapDim value can be obtained as follows:
var mapDim = $('mapElementId').getDimensions();
Return Value:
The return value is the maximum zoom level that will still display the entire bounds. This value will be between 0 and the maximum zoom level, inclusive.
The maximum zoom level is 21. (I believe it was only 19 for Google Maps API v2.)
Explanation:
Google Maps uses a Mercator projection. In a Mercator projection the lines of longitude are equally spaced, but the lines of latitude are not. The distance between lines of latitude increase as they go from the equator to the poles. In fact the distance tends towards infinity as it reaches the poles. A Google Maps map, however, does not show latitudes above approximately 85 degrees North or below approximately -85 degrees South. (reference) (I calculate the actual cutoff at +/-85.05112877980658 degrees.)
This makes the calculation of the fractions for the bounds more complicated for latitude than for longitude. I used a formula from Wikipedia to calculate the latitude fraction. I am assuming this matches the projection used by Google Maps. After all, the Google Maps documentation page I link to above contains a link to the same Wikipedia page.
Other Notes:
Zoom levels range from 0 to the maximum zoom level. Zoom level 0 is the map fully zoomed out. Higher levels zoom the map in further. (reference)
At zoom level 0 the entire world can be displayed in an area that is 256 x 256 pixels. (reference)
For each higher zoom level the number of pixels needed to display the same area doubles in both width and height. (reference)
Maps wrap in the longitudinal direction, but not in the latitudinal direction.
A similar question has been asked on the Google group: http://groups.google.com/group/google-maps-js-api-v3/browse_thread/thread/e6448fc197c3c892
The zoom levels are discrete, with the scale doubling in each step. So in general you cannot fit the bounds you want exactly (unless you are very lucky with the particular map size).
Another issue is the ratio between side lengths e.g. you cannot fit the bounds exactly to a thin rectangle inside a square map.
There's no easy answer for how to fit exact bounds, because even if you are willing to change the size of the map div, you have to choose which size and corresponding zoom level you change to (roughly speaking, do you make it larger or smaller than it currently is?).
If you really need to calculate the zoom, rather than store it, this should do the trick:
The Mercator projection warps latitude, but any difference in longitude always represents the same fraction of the width of the map (the angle difference in degrees / 360). At zoom zero, the whole world map is 256x256 pixels, and zooming each level doubles both width and height. So after a little algebra we can calculate the zoom as follows, provided we know the map's width in pixels. Note that because longitude wraps around, we have to make sure the angle is positive.
var GLOBE_WIDTH = 256; // a constant in Google's map projection
var west = sw.lng();
var east = ne.lng();
var angle = east - west;
if (angle < 0) {
angle += 360;
}
var zoom = Math.round(Math.log(pixelWidth * 360 / angle / GLOBE_WIDTH) / Math.LN2);
For version 3 of the API, this is simple and working:
var latlngList = [];
latlngList.push(new google.maps.LatLng(lat, lng));
var bounds = new google.maps.LatLngBounds();
latlngList.each(function(n) {
bounds.extend(n);
});
map.setCenter(bounds.getCenter()); //or use custom center
map.fitBounds(bounds);
and some optional tricks:
//remove one zoom level to ensure no marker is on the edge.
map.setZoom(map.getZoom() - 1);
// set a minimum zoom
// if you got only 1 marker or all markers are on the same address map will be zoomed too much.
if(map.getZoom() > 15){
map.setZoom(15);
}
Dart Version:
double latRad(double lat) {
final double sin = math.sin(lat * math.pi / 180);
final double radX2 = math.log((1 + sin) / (1 - sin)) / 2;
return math.max(math.min(radX2, math.pi), -math.pi) / 2;
}
double getMapBoundZoom(LatLngBounds bounds, double mapWidth, double mapHeight) {
final LatLng northEast = bounds.northEast;
final LatLng southWest = bounds.southWest;
final double latFraction = (latRad(northEast.latitude) - latRad(southWest.latitude)) / math.pi;
final double lngDiff = northEast.longitude - southWest.longitude;
final double lngFraction = ((lngDiff < 0) ? (lngDiff + 360) : lngDiff) / 360;
final double latZoom = (math.log(mapHeight / 256 / latFraction) / math.ln2).floorToDouble();
final double lngZoom = (math.log(mapWidth / 256 / lngFraction) / math.ln2).floorToDouble();
return math.min(latZoom, lngZoom);
}
Here a Kotlin version of the function:
fun getBoundsZoomLevel(bounds: LatLngBounds, mapDim: Size): Double {
val WORLD_DIM = Size(256, 256)
val ZOOM_MAX = 21.toDouble();
fun latRad(lat: Double): Double {
val sin = Math.sin(lat * Math.PI / 180);
val radX2 = Math.log((1 + sin) / (1 - sin)) / 2;
return max(min(radX2, Math.PI), -Math.PI) /2
}
fun zoom(mapPx: Int, worldPx: Int, fraction: Double): Double {
return floor(Math.log(mapPx / worldPx / fraction) / Math.log(2.0))
}
val ne = bounds.northeast;
val sw = bounds.southwest;
val latFraction = (latRad(ne.latitude) - latRad(sw.latitude)) / Math.PI;
val lngDiff = ne.longitude - sw.longitude;
val lngFraction = if (lngDiff < 0) { (lngDiff + 360) / 360 } else { (lngDiff / 360) }
val latZoom = zoom(mapDim.height, WORLD_DIM.height, latFraction);
val lngZoom = zoom(mapDim.width, WORLD_DIM.width, lngFraction);
return minOf(latZoom, lngZoom, ZOOM_MAX)
}
None of the highly upvoted answers worked for me. They threw various undefined errors and ended up calculating inf/nan for angles. I suspect perhaps the behavior of LatLngBounds has changed over time. In any case, I found this code to work for my needs, perhaps it can help someone:
function latRad(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;
}
function getZoom(lat_a, lng_a, lat_b, lng_b) {
let latDif = Math.abs(latRad(lat_a) - latRad(lat_b))
let lngDif = Math.abs(lng_a - lng_b)
let latFrac = latDif / Math.PI
let lngFrac = lngDif / 360
let lngZoom = Math.log(1/latFrac) / Math.log(2)
let latZoom = Math.log(1/lngFrac) / Math.log(2)
return Math.min(lngZoom, latZoom)
}
Thanks, that helped me a lot in finding the most suitable zoom factor to correctly display a polyline.
I find the maximum and minimum coordinates among the points I have to track and, in case the path is very "vertical", I just added few lines of code:
var GLOBE_WIDTH = 256; // a constant in Google's map projection
var west = <?php echo $minLng; ?>;
var east = <?php echo $maxLng; ?>;
*var north = <?php echo $maxLat; ?>;*
*var south = <?php echo $minLat; ?>;*
var angle = east - west;
if (angle < 0) {
angle += 360;
}
*var angle2 = north - south;*
*if (angle2 > angle) angle = angle2;*
var zoomfactor = Math.round(Math.log(960 * 360 / angle / GLOBE_WIDTH) / Math.LN2);
Actually, the ideal zoom factor is zoomfactor-1.
Since all of the other answers seem to have issues for me with one or another set of circumstances (map width/height, bounds width/height, etc.) I figured I'd put my answer here...
There was a very useful javascript file here: http://www.polyarc.us/adjust.js
I used that as a base for this:
var com = com || {};
com.local = com.local || {};
com.local.gmaps3 = com.local.gmaps3 || {};
com.local.gmaps3.CoordinateUtils = new function() {
var OFFSET = 268435456;
var RADIUS = OFFSET / Math.PI;
/**
* Gets the minimum zoom level that entirely contains the Lat/Lon bounding rectangle given.
*
* #param {google.maps.LatLngBounds} boundary the Lat/Lon bounding rectangle to be contained
* #param {number} mapWidth the width of the map in pixels
* #param {number} mapHeight the height of the map in pixels
* #return {number} the minimum zoom level that entirely contains the given Lat/Lon rectangle boundary
*/
this.getMinimumZoomLevelContainingBounds = function ( boundary, mapWidth, mapHeight ) {
var zoomIndependentSouthWestPoint = latLonToZoomLevelIndependentPoint( boundary.getSouthWest() );
var zoomIndependentNorthEastPoint = latLonToZoomLevelIndependentPoint( boundary.getNorthEast() );
var zoomIndependentNorthWestPoint = { x: zoomIndependentSouthWestPoint.x, y: zoomIndependentNorthEastPoint.y };
var zoomIndependentSouthEastPoint = { x: zoomIndependentNorthEastPoint.x, y: zoomIndependentSouthWestPoint.y };
var zoomLevelDependentSouthEast, zoomLevelDependentNorthWest, zoomLevelWidth, zoomLevelHeight;
for( var zoom = 21; zoom >= 0; --zoom ) {
zoomLevelDependentSouthEast = zoomLevelIndependentPointToMapCanvasPoint( zoomIndependentSouthEastPoint, zoom );
zoomLevelDependentNorthWest = zoomLevelIndependentPointToMapCanvasPoint( zoomIndependentNorthWestPoint, zoom );
zoomLevelWidth = zoomLevelDependentSouthEast.x - zoomLevelDependentNorthWest.x;
zoomLevelHeight = zoomLevelDependentSouthEast.y - zoomLevelDependentNorthWest.y;
if( zoomLevelWidth <= mapWidth && zoomLevelHeight <= mapHeight )
return zoom;
}
return 0;
};
function latLonToZoomLevelIndependentPoint ( latLon ) {
return { x: lonToX( latLon.lng() ), y: latToY( latLon.lat() ) };
}
function zoomLevelIndependentPointToMapCanvasPoint ( point, zoomLevel ) {
return {
x: zoomLevelIndependentCoordinateToMapCanvasCoordinate( point.x, zoomLevel ),
y: zoomLevelIndependentCoordinateToMapCanvasCoordinate( point.y, zoomLevel )
};
}
function zoomLevelIndependentCoordinateToMapCanvasCoordinate ( coordinate, zoomLevel ) {
return coordinate >> ( 21 - zoomLevel );
}
function latToY ( lat ) {
return OFFSET - RADIUS * Math.log( ( 1 + Math.sin( lat * Math.PI / 180 ) ) / ( 1 - Math.sin( lat * Math.PI / 180 ) ) ) / 2;
}
function lonToX ( lon ) {
return OFFSET + RADIUS * lon * Math.PI / 180;
}
};
You can certainly clean this up or minify it if needed, but I kept the variable names long in an attempt to make it easier to understand.
If you are wondering where OFFSET came from, apparently 268435456 is half of earth's circumference in pixels at zoom level 21 (according to http://www.appelsiini.net/2008/11/introduction-to-marker-clustering-with-google-maps).
Valerio is almost right with his solution, but there is some logical mistake.
you must firstly check wether angle2 is bigger than angle, before adding 360 at a negative.
otherwise you always have a bigger value than angle
So the correct solution is:
var west = calculateMin(data.longitudes);
var east = calculateMax(data.longitudes);
var angle = east - west;
var north = calculateMax(data.latitudes);
var south = calculateMin(data.latitudes);
var angle2 = north - south;
var zoomfactor;
var delta = 0;
var horizontal = false;
if(angle2 > angle) {
angle = angle2;
delta = 3;
}
if (angle < 0) {
angle += 360;
}
zoomfactor = Math.floor(Math.log(960 * 360 / angle / GLOBE_WIDTH) / Math.LN2) - 2 - delta;
Delta is there, because i have a bigger width than height.
map.getBounds() is not momentary operation, so I use in similar case event handler. Here is my example in Coffeescript
#map.fitBounds(#bounds)
google.maps.event.addListenerOnce #map, 'bounds_changed', =>
#map.setZoom(12) if #map.getZoom() > 12
Work example to find average default center with react-google-maps on ES6:
const bounds = new google.maps.LatLngBounds();
paths.map((latLng) => bounds.extend(new google.maps.LatLng(latLng)));
const defaultCenter = bounds.getCenter();
<GoogleMap
defaultZoom={paths.length ? 12 : 4}
defaultCenter={defaultCenter}
>
<Marker position={{ lat, lng }} />
</GoogleMap>
The calculation of the zoom level for the longitudes of Giles Gardam works fine for me.
If you want to calculate the zoom factor for latitude, this is an easy solution that works fine:
double minLat = ...;
double maxLat = ...;
double midAngle = (maxLat+minLat)/2;
//alpha is the non-negative angle distance of alpha and beta to midangle
double alpha = maxLat-midAngle;
//Projection screen is orthogonal to vector with angle midAngle
//portion of horizontal scale:
double yPortion = Math.sin(alpha*Math.pi/180) / 2;
double latZoom = Math.log(mapSize.height / GLOBE_WIDTH / yPortion) / Math.ln2;
//return min (max zoom) of both zoom levels
double zoom = Math.min(lngZoom, latZoom);
For swift version
func getBoundsZoomLevel(bounds: GMSCoordinateBounds, mapDim: CGSize) -> Double {
var bounds = bounds
let WORLD_DIM = CGSize(width: 256, height: 256)
let ZOOM_MAX: Double = 21.0
func latRad(_ lat: Double) -> Double {
let sin2 = sin(lat * .pi / 180)
let radX2 = log10((1 + sin2) / (1 - sin2)) / 2
return max(min(radX2, .pi), -.pi) / 2
}
func zoom(_ mapPx: CGFloat,_ worldPx: CGFloat,_ fraction: Double) -> Double {
return floor(log10(Double(mapPx) / Double(worldPx) / fraction / log10(2.0)))
}
let ne = bounds.northEast
let sw = bounds.southWest
let latFraction = (latRad(ne.latitude) - latRad(sw.latitude)) / .pi
let lngDiff = ne.longitude - sw.longitude
let lngFraction = lngDiff < 0 ? (lngDiff + 360) : (lngDiff / 360)
let latZoom = zoom(mapDim.height, WORLD_DIM.height, latFraction);
let lngZoom = zoom(mapDim.width, WORLD_DIM.width, lngFraction);
return min(latZoom, lngZoom, ZOOM_MAX)
}
Calculate zoom level to display a map including the two cross corners of the area and display the map on a the part of the screen with a specific height.
Two coordinates
max lat/long
min lat/long
Display area in pixels
height
double getZoomLevelNew(context,
double maxLat, double maxLong,
double minLat, double minLong,
double height){
try {
double _zoom;
MediaQueryData queryData2;
queryData2 = MediaQuery.of(context);
double _zLat =
Math.log(
(globals.factor(height) / queryData2.devicePixelRatio / 256.0) *
180 / (maxLat - minLat).abs()) / Math.log(2);
double _zLong =
Math.log((globals.factor(MediaQuery
.of(context)
.size
.width) / queryData2.devicePixelRatio / 256.0) * 360 /
(maxLong - minLong).abs()) / Math.log(2);
_zoom = Math.min(_zLat, _zLong)*globals.zoomFactorNew;
if (_zoom < 0) {
_zoom = 0;
}
return _zoom;
} catch(e){
print("getZoomLevelNew - excep - " + e.toString());
}

google maps. how to create a LatLngBounds rectangle (square) given coords of a central point

I have a point (X,Y) and I want to create a square , Google maps LatLngBounds object so to make geocode requests bias only into this LatLngBound region.
How can I create such a LatLngBounds square with center the given point? I have to find the NE and SW point. But how can I find it given a distance d and a point (x,y)?
Thanks
You can also getBounds from a radius defined as a circle and leave the trig to google.
new google.maps.Circle({center: latLng, radius: radius}).getBounds();
well that's very complicated. for a rough box try this:
if (typeof(Number.prototype.toRad) === "undefined") {
Number.prototype.toRad = function() {
return this * Math.PI / 180;
}
}
if (typeof(Number.prototype.toDeg) === "undefined") {
Number.prototype.toDeg = function() {
return this * 180 / Math.PI;
}
}
var dest = function(lat,lng,brng, dist) {
this._radius = 6371;
dist = typeof(dist) == 'number' ? dist : typeof(dist) == 'string' && dist.trim() != '' ? +dist : NaN;
dist = dist / this._radius;
brng = brng.toRad();
var lat1 = lat.toRad(),
lon1 = lng.toRad();
var lat2 = Math.asin(Math.sin(lat1) * Math.cos(dist) + Math.cos(lat1) * Math.sin(dist) * Math.cos(brng));
var lon2 = lon1 + Math.atan2(Math.sin(brng) * Math.sin(dist) * Math.cos(lat1), Math.cos(dist) - Math.sin(lat1) * Math.sin(lat2));
lon2 = (lon2 + 3 * Math.PI) % (2 * Math.PI) - Math.PI;
return (lat2.toDeg() + ' ' + lon2.toDeg());
}
var northEastCorner = dest(centreLAT,centreLNG,45,10);
var southWestCorner = dest(centreLAT,centreLNG,225,10);
EDIT
The above was they way to do it way back in 2011 when I wrote it. These days the google maps api has come on a loooong way. The answer by #wprater is much neater and uses some of the newer api methods.
Wouldn't it work to simply add/subtract d/2 to your x/y locations?
Given x,y as the center point:
NW = x-(d/2),y-(d/2)
SE = x+(d/2),y+(d/2)
Don't trust me on this, though - I am terrible at math :)
This assumes d as a "diameter", rather than a radius. If "d" is the radius, don't bother with the divide-by-two part.