I am trying to setup a layer using worldwind java and i want to render icons on the map at their specific geo locations. I have that working but i want to be able to zoom to where all the icons are. Is there an easy way to do that? Im not really sure where to start.. Are there existing methods for zooming in on a group of points?
First you need to calculate the Sector containing all of your points. e.g.
Sector boundingSector = Sector.boundingSector(points);
//public static Sector boundingSector(Iterable<? extends LatLon> itrbl)
Now here's some code taken from ScankortDenmark example to calculate the zoom you need to fit the whole sector on screen:
// From ScankortDenmark example
public static double computeZoomForExtent(Sector sector)
{
Angle delta = sector.getDeltaLat();
if (sector.getDeltaLon().compareTo(delta) > 0)
delta = sector.getDeltaLon();
double arcLength = delta.radians * Earth.WGS84_EQUATORIAL_RADIUS;
double fieldOfView = Configuration.getDoubleValue(AVKey.FOV, 45.0);
return arcLength / (2 * Math.tan(fieldOfView / 2.0));
}
182Much's answer does work under under some conditions. However, a better solution must take into account that the Horizontal FOV (Field of View) is not always fixed at 45.0 degrees. It also needs to take into account the Vertical FOV. Even how the positions end of clustering has to be taken into account. Meaning, do the positions spread out more East to West or North and South. Is the users view of the globe (WorldWindow) actually skinnier then the height. All of these factors come into account when calculating the needed zoom level to view all positions. I created this static method to account for all of the listed positions above. As a side note, you can have slightly better precision if you calculate the actual mean radius of the Earth for where your positions tend to cluster instead of taken Earth.WGS84_EQUATORIAL_RADIUS. But this is almost negligible so I leave that part out here.
/**
* Calculates the altitude in meters needed to view all of the given points.
* This method is safe for any window sizing configurations. If the
* WorldWindor arg is null then a static max altitude value of 1,0667,999
* meters is returned. if the WorldWindow is good but the list of Positions
* is null or empty then the current zoom level of the WorldWindow is
* returned. If the list of positions cannot all be seen on the globe
* because some positions are on the other side of the globe then a static
* max altitude value of 1,0667,999 meters is returned.
*
* #param positions
* - a list of positions wanted to view
* #return the altitude in meters needed to view all of the given points.
*/
public static double getZoomAltitude(List<Position> positions, WorldWindow wwd) {
double zoom = 10667999;
if (wwd != null) {
// Gets the current zoom as a fail safe to return
BasicOrbitView orbitView = (BasicOrbitView) wwd.getView();
zoom = orbitView.getZoom();
// zoom is in meters and and is limited the max zoom out to 10,667,999 meters
int MAX_ZOOM = 10667999;
if (positions != null && !positions.isEmpty()) {
Sector sector = Sector.boundingSector(positions);
if (sector != null) {
// This calculation takes into account the window sizing configuration of the map in order to accurately
// display the list of positions.
double meanRadius = Earth.WGS84_EQUATORIAL_RADIUS;
// Next we must calculate the zoom levels for both delta latitude viewing and delta longitude viewing.
// generally, a group of positions that spread out more Longitudenal viewing (wider viewing width)
// holds a constant 45.0 degree field of view (FOV). The horizontal FOV can be changed so this input
// must handle dynamically as well. The latitudenal (positon group runs more East to West then North and South)
// position group have a dynamic FOV that changes depending on the users sizing of the map. These have
// to be handled any time the group of positions has a greater delta latitude than delta longitude.
// Also if the user has a skinny map this will effect the output calculation and must be handled.
// Here we take all the dynamic variables into account for both types of possibilities and choose
// the larger zoom level of them.
int deltaLon = new BigDecimal(sector.getDeltaLon().radians * meanRadius).intValue();
int deltaLat = new BigDecimal(sector.getDeltaLat().radians * meanRadius).intValue();
System.out.println("deltaLonAL Wider: " + deltaLon + "\tdeltaLatAL Taller: " + deltaLat);
double horizontalFOV = orbitView.getFieldOfView().getDegrees();
double verticalFOV = ViewUtil.computeVerticalFieldOfView(orbitView.getFieldOfView(),
orbitView.getViewport()).getDegrees();
double lonZoomLevel = new BigDecimal((deltaLon / 2.0) / (Math.tan(horizontalFOV / 2.0))).intValue();
double latZoomLevel = new BigDecimal((deltaLat / 2.0)
/ (Math.tan(Math.toRadians(verticalFOV) / 2.0))).intValue();
System.out
.println("LonZoomLevel Wider: " + lonZoomLevel + "\tLatZoomLevel Taller: " + latZoomLevel);
double zoomLevel = Math.max(lonZoomLevel, latZoomLevel);
System.out.println("zoomLevel meters: " + zoomLevel + "\tfeet: "
+ new BigDecimal(zoomLevel * 3.2808));
// zoom is the altitude measured in meters to view a given area calculated to fit the viewing
// window edge to edge. A buffer is needed around the area for visual appeal. The bufferedZoom
// is a calculated linear equation (y = 1.0338x + 96177 where R² = 1) It gives the same buffer
// boundary around a group of position depending on the calculated zoom altitude.
double bufferedZoom = 1.0338 * zoomLevel + 96177;
zoom = new BigDecimal(bufferedZoom).intValue();
if (zoom > MAX_ZOOM) {
zoom = MAX_ZOOM;
System.out.println("MAX_ZOOM applied");
}
}
} else {
System.out.println("getZoomAltitude method cannot calculate the zoom because the points passed in was null and the current zoom was returned.");
}
}
return zoom;
}
Related
I got zoom level of 3-21. zoom out-zoom in respectively.
But i want to compute that zoom level to meter/km distance with the given bar(screenwidth?) of the screen like Gmaps scale bar do.
Example :
zoom level 20 = hundreds of meter
zoom levevl 3 = thousands of km or a mile
TIA
You can find the radius between right/left most point and center of the map when map becomes idle by using
/**
* Method used to fetch Map radius
*/
private int getMapRadius() {
LatLng latlng = googleMap.getProjection().getVisibleRegion().latLngBounds.getCenter();
if (latLng == null)
return 0;
LatLng latLng1 = googleMap.getProjection().getVisibleRegion().farRight;
return (int) MapUtils.computeDistance(latLng1.latitude, latLng1.longitude, latLng.latitude, latLng.longitude);
}
Please take a look at my other question which this was done incorrectly here.
What I need to do
I have an image of a map of my country, I took the piece of the map from Google Maps, so that means I know all of the corner's coordinates in longitude and latitude. My program needs to show up the map, and paint targets on it where each target has its only longitude and latitude, similar to how radar displays targets.
The problem
The problem with my solution is that it's not really using real mathematical formulas to get the X, Y position. It uses simple division and multiple by ratio with minimum and maximum as you can see in my other question.
this is the method:
protected Location getCoordinatesByGlobe(float latitude, float longitude) {
/**
* Work out minimum and maximums, clamp inside map bounds
*/
latitude = Math.max(mapLatitudeMin, Math.min(mapLatitudeMax, latitude));
longitude = Math.max(mapLongitudeMin, Math.min(mapLongitudeMax, longitude));
/**
* We need the distance from 0 or minimum long/lat
*/
float adjLon = longitude - mapLongitudeMin;
float adjLat = latitude - mapLatitudeMin;
float mapLongWidth = mapLongitudeMax - mapLongitudeMin;
float mapLatHeight = mapLatitudeMax - mapLatitudeMin;
float mapWidth = mapImage.getWidth();
float mapHeight = mapImage.getHeight();
float longPixelRatio = mapWidth / mapLongWidth;
float latPixelRatio = mapHeight / mapLatHeight;
int x = Math.round(adjLon * longPixelRatio) - 3;// these are offsets for the target icon that shows.. eedit laterrr #oz
int y = Math.round(adjLat * latPixelRatio) + 3; //
// turn it up
y = (int) (mapHeight - y);
return new Location(x, y);
}
What I have tried
So I was a bit with myself tried to think of something logical, on how can I do this. And I came up with something that doesn't really work exactly:
If we have the corner top-left for example coordinates (Longitude and latitude), and we have the coordinates of the target that we want to display, that means we can do distanceToPoint to know how many kilometers far from the start it is.
After that, we need to know the heading to that point, so we do calculateHeading which gives us the angle to the target point.
So lets call A the starting point (top-left corner)
float aLat = 33.49f;
float aLong = 33.69f;
And our target point we call it b:
float bLat = 32f;
float bLong = 35f;
And then we can calculate the distance from A to B in kilometers:
double km = distanceTopPoint(aLat, aLong, bLat, bLong);
And then we calculate the angle to the point:
double angle = calculateHeading(aLat, aLong, bLat, bLong);
And if we have the km distance and angle, we can know the distance in km for longitude and latitude:
int latDistance = (int) Math.round(km * Math.cos(angle));
int lonDistance = (int) Math.round(km * Math.sin(angle));
So now I probably have the distance from the longitude to the target's longitude and same for latitude. But what can I do with this information?
I tried thinking again, and I figured out that I can know the distance from the left top corner to the right top corner distance in km and same for top left corner to top left bottom corner. And then I can do width / km to get the km per pixel.
But I am really unsure, im sure that I am doing something wrong.
Any ideas?
The Mercator projection is a cylindrical projection, i.e. the generalized coordinates can be calculated as:
a = longitude
b = tan(latitude)
These are unscaled coordinates, i.e. they do not correspond to pixel positions.
Let's say you have an image of w x h pixels that represents the area between (min_long, min_lat) - (max_long, max_lat). These coordinates can be converted to generalized projected coordinates with the above formulas, which yields (min_a, min_b) - (max_a, max_b).
Now you need a linear mapping of the generalized coordinates to pixel positions. This linear mapping can be expressed with four parameters (two scaling parameters and two offset parameters):
x = s_a * a + o_a
y = s_b * b = o_a
Therefore, you need to find the four parameters. You know that the top left corner has pixel coordinates (0, 0) and generalized coordinates (min_a, max_b). Similarly for the bottom right corner. This gives you four constraints and a linear system of equations:
0 = s_a * min_a + o_a
0 = s_b * max_b + o_b
w = s_a * max_a + o_a
h = s_b * min_b + o_b
The solution of this system is:
s_a = w / (max_a - min_a)
o_a = -w * min_a / (max_a - min_a)
s_b = -h / (max_b - min_b)
o_b = h * max_b / (max_b - min_b)
And this is it. If you want the pixel coordinates for some arbitrary point `(long, lat), then do the following:
Calculate the generalized coordinates a and b (be careful to use radians when calculating the tangens).
Use the linear map to convert a and b to pixel coordinates x and y with the pre-calculated parameters.
Inversion
To get latitude and longitude from pixel coordinates, do the following:
Calculate the generalized coordinates:
a = (x - o_a) / s_a
b = (x - o_b) / s_b
Calculate the geo-coordinates:
longitude = a
latitude = arc tan (b)
Again, be careful about radians/degrees.
I'm developing an augmented reality application using the ARToolkit. I would like to add a feature where I would control an object's size, or control the volume of a played song, by rotating a specific marker. I've found an example of an application, which is returning a 4x4 matrix that contains position and rotation information of the marker.
An example of such a matrix:
000,1878 -000,9442 -000,2707 -002,2898
-000,6210 000,0994 -000,7775 117,8998
-000,7610 -000,3141 000,5677 -530,6667
000,0000 000,0000 000,0000 001,0000
I've found a formula and a corresponding java method for the decomposition of the matrix to all three rotation angles, but I'm confused by the returned angle values.
The java method:
/** this conversion uses conventions as described on page:
* http://www.euclideanspace.com/maths/geometry/rotations/euler/index.htm
* Coordinate System: right hand
* Positive angle: right hand
* Order of euler angles: heading first, then attitude, then bank
* matrix row column ordering:
* [m00 m01 m02]
* [m10 m11 m12]
* [m20 m21 m22]*/
public final void rotate(matrix m) {
// Assuming the angles are in radians.
if (m.m10 > 0.998) { // singularity at north pole
heading = Math.atan2(m.m02,m.m22);
attitude = Math.PI/2;
bank = 0;
return;
}
if (m.m10 < -0.998) { // singularity at south pole
heading = Math.atan2(m.m02,m.m22);
attitude = -Math.PI/2;
bank = 0;
return;
}
heading = Math.atan2(-m.m20,m.m00);
bank = Math.atan2(-m.m12,m.m11);
attitude = Math.asin(m.m10);
}
Example of the returned values:
Heading: 1.384716377951241,
Bank: 1.3919044985590532
Attitude: -0.7751361901097762
So the result, obviously, isn't in degrees, which I would want. I'm I doing this the right way? What am I doing wrong?
I am trying to calculate the height above map (ignoring topography) given a zoom level. I know the equation for scale at a specific zoom level is 591657550.5/2^(level-1) (https://gis.stackexchange.com/questions/7430/google-maps-zoom-level-ratio), but I am unsure on how to use this information (or whether or not this is the right information) to solve for height above map. Any help is appreciated.
I set my google map size to 5cm selected a zoom level, and then re-found that location with that zoom in google earth to get a eye altitude level (the D value in the angular size equation http://en.wikipedia.org/wiki/Forced_perspective). I was able to find the h value in the angular size equation by first setting my map length on screen to 5cm, and then using the scale equation of 591657550.5/2^(level-1) *5cm to calculate the h value in the angular size equation. Knowing these two variables I was able to calculate the constant angle for which google maps displayed images when maps was at a 5cm width (85.36222058). From these pieces of information I was able to construct this method which calculates eye altitude above map from zoom level with relative accuracy
public float getAltitude(float mapzoom){
//this equation is a transformation of the angular size equation solving for D. See: http://en.wikipedia.org/wiki/Forced_perspective
float googleearthaltitude;
float firstPartOfEq= (float)(.05 * ((591657550.5/(Math.pow(2,(mapzoom-1))))/2));//amount displayed is .05 meters and map scale =591657550.5/(Math.pow(2,(mapzoom-1))))
//this bit ^ essentially gets the h value in the angular size eq then divides it by 2
googleearthaltitude =(firstPartOfEq) * ((float) (Math.cos(Math.toRadians(85.362/2)))/(float) (Math.sin(Math.toRadians(85.362/2))));//85.362 is angle which google maps displays on a 5cm wide screen
return googleearthaltitude;
}
Sorry if my explanation is poorly explained. If you guys want to use this method feel free to. Sorry for any poorly worded sentences.
I have basically converted Javascript code to Java. I hope this works.
public int convertRangeToZoom(double range) {
//see: google.maps.v3.all.debug.js
int zoom = (int) Math.round(Math.log(35200000 / range) / Math.log(2));
if (zoom < 0) zoom = 0;
else if (zoom > 19) zoom = 19;
return zoom;
}
public int convertZoomToRange(double zoom){
//see: google.maps.v3.all.debug.js
int range = (int) 35200000/(Math.pow(2, zoom));
if (range < 300) range = 300;
return range;
}
https://groups.google.com/forum/#!msg/google-earth-browser-plugin/eSL9GlAkWBk/T4mdToJz_FgJ
I am trying to center a Google Map to the user location while giving a reasonable zoom level taking into account the accuracy of that location. Could anybody describe how should I compute it? Which variables are involved, how do you achieve this?
What you are looking for is the formula that calculates the zoom level based on the accuracy of the location.
I managed to come up with this formula which (in my tests) worked pretty well.
This can be simplified (might not seem so) to this:
This scary looking thing is what you want.
EquatorLength is 40,075,004 meters. While the Meters/Pixel can be calculated by diving the diameter of the accuracy circle by the length of the device screen (in pixels).
Here's a sample program that I used to test this formula:
GoogleMap mMap;
#Override
protected void onStart() {
super.onStart();
mMap = ((MapFragment)getFragmentManager().findFragmentById(R.id.map)).getMap();
// Enable user's location layer
mMap.setMyLocationEnabled(true);
mMap.setOnMyLocationChangeListener(new GoogleMap.OnMyLocationChangeListener() {
#Override
public void onMyLocationChange(Location location) {
// Location lat-lng
LatLng loc = new LatLng(location.getLatitude(), location.getLongitude());
// Location accuracy diameter (in meters)
float accuracy = location.getAccuracy() * 2;
// Screen measurements
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
// Use min(width, height) (to properly fit the screen
int screenSize = Math.min(metrics.widthPixels, metrics.heightPixels);
// Equators length
long equator = 40075004;
// The meters per pixel required to show the whole area the user might be located in
double requiredMpp = accuracy/screenSize;
// Calculate the zoom level
double zoomLevel = ((Math.log(equator / (256 * requiredMpp))) / Math.log(2)) + 1;
Log.e(TAG, String.format("Accuracy: %f. Screen Width: %d, Height: %d",
accuracy, metrics.widthPixels, metrics.heightPixels));
Log.e(TAG, String.format("Required M/Px: %f Zoom Level: %f Approx Zoom Level: %d",
requiredMpp, zoomLevel, calculateZoomLevel(screenSize, accuracy)));
// Center to user's position
mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(loc, (float) zoomLevel));
// Prevent the camera centering on the user again
mMap.setOnMyLocationChangeListener(null);
}
});
}
private int calculateZoomLevel(int screenWidth, float accuracy) {
double equatorLength = 40075004; // in meters
double metersPerPixel = equatorLength / 256;
int zoomLevel = 1;
while ((metersPerPixel * (double) screenWidth) > accuracy) {
metersPerPixel /= 2;
zoomLevel++;
}
return zoomLevel;
}
Few things to note:
This answer is based on this and implements it to check the values generated
Accuracy is the radius of user's location and according to the docs it can be up to 68% correct.
Any corrections are very welcome.
If you're looking for something simple:
var zoom = Math.min(20, Math.max(1, Math.log2(591657550/accuracy)-2));
Tweak -2 to get the desired zoom.
Checkout this answer for a chart corresponding zoom with accuracy.
Thanks #Simas! I picked up your algo to make this extension to GMSMapView to calculate for the ideal zoomLevel given the accuracy of a CLLocation.
I had to make adjustments to consider devices with Retina displays since each pixel is not exactly the same as 1 point on the screen:
extension GMSMapView {
func getIdealZoomLevel(usingLocation location:CLLocation)->Float {
let retinaScale = Double(UIScreen.mainScreen().scale)
let equatorLength : Double = 40075004 // in meters
var metersPerPixel = equatorLength / 256
let accuracy = location.horizontalAccuracy
// I used height because I'm on landscape, but moving forward I'll have to get the Min of the width and height.
// I also took only 75% of the height to give it some margin
let screenWidth : Double = Double( self.frame.size.height) * 0.75
var display = metersPerPixel * (screenWidth / retinaScale)
var zoomLevel : Float = 0.0
while (display > accuracy) {
metersPerPixel /= 2
display = metersPerPixel * (screenWidth / retinaScale)
zoomLevel += 1
}
return zoomLevel
}
}
It's for a Swift project I'm working on and right now, I'm able to display a good proximity enclosing the radius within the given CLLocation.
Hope this helps.