问题
Does anyone know of a formula that I can use to convert from Zoom in Google Maps Android API: https://developers.google.com/maps/documentation/android-api/streetview to the FOV used in the Google StreetView Image API: https://developers.google.com/maps/documentation/streetview/intro?
I found a couple of old formulas here:
- https://groups.google.com/forum/#!topic/google-maps-js-api-v3/uqKfg0ZBhWc
- https://groups.google.com/forum/#!msg/google-maps-image-apis/O_Odb0A7_0c/Q74cHCoRuscJ
I then put them to a test whereby the FOV will be calculated when I zoom the camera on my android phone:
FOV1: 3.9018*Math.pow(streetViewPanoramaCamera.zoom,2) - 42.432*streetViewPanoramaCamera.zoom + 123
FOV2: Math.abs(streetViewPanoramaCamera.zoom/5*108-120
FOV3: 180/Math.pow(2,streetViewPanoramaCamera.zoom)
@Override
public void onStreetViewPanoramaCameraChange(StreetViewPanoramaCamera streetViewPanoramaCamera) {
Log.e("Pano", "Bearing: " + streetViewPanoramaCamera.bearing + " Tilt: " + streetViewPanoramaCamera.tilt +
" Zoom: " + streetViewPanoramaCamera.zoom +
" FOV1: "+ String.valueOf(3.9018*Math.pow(streetViewPanoramaCamera.zoom,2) - 42.432*streetViewPanoramaCamera.zoom + 123) +" FOV2: "+
Math.abs(streetViewPanoramaCamera.zoom/5*108-120) +" FOV3: "+ 180/Math.pow(2,streetViewPanoramaCamera.zoom) ) ;
}
At a zoom of 0.0 on android, we have the following FOV values returned:
09-27 15:53:52.322 E/Pano﹕ Bearing: 228.28955 Tilt: 14.516191 Zoom: 0.0 FOV1: 123.0 FOV2: 120.0 FOV3: 180.0
Since FOV has a max of 120, FOV2's formula seemed promising at first but when I zoomed at 2 times, it gave me a value of 76.8 which is far off from the actual:
09-27 16:01:48.235 E/Pano﹕ Bearing: 223.11241 Tilt: 1.852709 Zoom: 2.0 FOV1: 53.7432 FOV2: 76.8 FOV3: 45.0
This is the image on my phone after the 2 times zoom:
This is the image downloaded from Google Streetview Image API at FOV 76.8 (https://maps.googleapis.com/maps/api/streetview?size=600x300&location=-33.87365,151.20689&heading=223.11241&pitch=1.852709&fov=76.8&key=APIKEY):
This is the image downloaded from Google Streetview Image API at FOV 45 (https://maps.googleapis.com/maps/api/streetview?size=600x300&location=-33.87365,151.20689&heading=223.11241&pitch=1.852709&fov=45&key=APIKEY):
The closest I have gotten is if I made the FOV 26 but that is me guessing and not using the formulas - image below is at FOV 26:
回答1:
I finally had to regress the line to get the exponential formula out.
I used this website and manually typed in combinations of data points of both FOV and zoom values that worked well together: http://www.had2know.com/academics/regression-calculator-statistics-best-fit.html
My data points were:
FOV (ZOOM in brackets next to FOV)
120 (0)
50 (1)
45 (1.2)
37 (1.5)
26 (2)
69 (0.5)
78 (0.3)
31 (1.9)
To get a closer formula, you could continuously add on more "good-fit" data points but I just didn't have the time - anyone else who would like to perfect the formula can attempt it though.
I got the formula below where Y is the FOV and X is the zoom.
Y = 103.7587(0.5051^X)
correlation = -0.9882
回答2:
You can use following formulas (code in JavaScript):
Convert fov to zoom:
zoom = Math.log(180/
fov
)/(Math.log(2))
Convert zoom to fov:
fov = 180 / Math.pow(2,
zoom
)
回答3:
Googling this answer I could only find formulas for the web. Web is different because first of all, there is a Zoom vs FOV chart in the documentation: https://developers.google.com/maps/documentation/javascript/streetview#TilingPanoramas
And therefore one can come up with a formula. The Android documentation does not have such a table, and the formulas people are using for web do not work for Android (not even close).
There is some good news however, and it is this function available in the Android API: StreetViewPanorama.orientationToPoint(StreetViewPanoramaOrientation orientation)
This function allows us to provide an orientation, and it will return a point on the screen where that orientation lies. The documentation for this function says it will return null if that orientation is off the screen, but in practice I have found it will give a point outside the viewable area of the screen; however if we know the screen dimensions we can check whether or not the point is valid. By looping through 180 degrees, we can find how much of that range is viewable, and therefore the field of view.
WARNING: This function is inefficient. Only call if the zoom has changed!
NOTE: The sample code below calculates the HORIZONTAL field of view. To calculate the vertical field of view, you will need to make some changes. For example, change point.x to point.y, change all instances of screenSize.x to screenSize.y, change all instances of streetViewPanoramaCamera.bearing to streetViewPanoramaCamera.tilt, and change all instances of:
orientation = new StreetViewPanoramaOrientation(streetViewPanoramaCamera.tilt, angleCheck);
to:
orientation = new StreetViewPanoramaOrientation(angleCheck, streetViewPanoramaCamera.bearing);
Here is the simple approach, where we just loop from -90 to +90 degrees, and see how much of that 180 degrees is in the viewable area (Note: this only works if the tilt is 0 degrees, ie the user is not looking up or down):
private StreetViewPanorama mStreetViewPanorama;
private float mZoom = -1f;
private int mFieldOfViewDegrees;
private void onOrientationUpdate(StreetViewPanoramaCamera streetViewPanoramaCamera) {
if (streetViewPanoramaCamera.zoom != mZoom) {
mFieldOfViewDegrees = getFieldOfView(streetViewPanoramaCamera);
mZoom = streetViewPanoramaCamera.zoom;
}
}
private int getFieldOfView(StreetViewPanoramaCamera streetViewPanoramaCamera) {
WindowManager windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
Display display = windowManager.getDefaultDisplay();
Point screenSize = new Point();
display.getSize(screenSize);
StreetViewPanoramaOrientation orientation;
Integer lowerFovDegrees = null;
Integer upperFovDegrees = null;
for (int angleCheck = (int) streetViewPanoramaCamera.bearing - 90;
angleCheck <= (int) streetViewPanoramaCamera.bearing + 90;
angleCheck++) {
orientation = new StreetViewPanoramaOrientation(streetViewPanoramaCamera.tilt, angleCheck);
Point point = mStreetViewPanorama.orientationToPoint(orientation);
if (lowerFovDegrees == null) {
if ((point != null) && (point.x >= 0)) {
lowerFovDegrees = angleCheck;
}
} else if (upperFovDegrees == null) {
if ((point == null) || (point.x > screenSize.x)) {
upperFovDegrees = angleCheck - 1;
break;
}
}
}
int fieldOfViewDegrees = upperFovDegrees - lowerFovDegrees;
return fieldOfViewDegrees;
}
However, orientationToPoint() is expensive and this can call it up to 180 times. We can use a binary search to make this much more efficient (although more complex), and reduce it to more like ~13 calls. In this case, I have used recursive functions to perform the binary search. Also there is a limitation where the horizontal equator must be in view or the FOV cannot be accurately calculated; in this case it returns null. So long as the user doesn't tilt all the way down or up this isn't really a problem, but if they do the FOV will return null and you need to hide any UI elements that depend on the FOV being accurate (or they will not be in the correct location).
private StreetViewPanorama mStreetViewPanorama;
private float mZoom = -1f;
private float mFieldOfViewDegrees;
private void onOrientationUpdate(StreetViewPanoramaCamera streetViewPanoramaCamera) {
if (streetViewPanoramaCamera.zoom != mZoom) {
Float fieldOfViewDegrees = getFieldOfView(streetViewPanoramaCamera);
if (fieldOfViewDegrees == null) { // If FOV cannot be determined, hide any overlay UI overlay elements so they don't display misaligned
mZoom = -1f;
// Hide UI overlay elements
} else {
mFieldOfViewDegrees = fieldOfViewDegrees;
mZoom = streetViewPanoramaCamera.zoom;
// Show UI overlay elements
}
}
}
/*
* Determine field of view. Must use roundabout way since the info is not provided directly.
* StreetViewPanorama.orientationToPoint() will tell us if a particular orientation is visible on the
* screen, so we can loop from looking left (-90) to right (+90) and see how much of the 180 degrees is in
* the field of view.
*
* This is is CPU intensive, so instead of a simple loop we use a recursive binary search, and make sure
* to only call this function when the zoom has changed.
*
* WARNING: This method of getting the FOV only works if the equator is still in view. If the user tilts
* too far down or up, it will return null.
*/
private Float getFieldOfView(StreetViewPanoramaCamera streetViewPanoramaCamera) {
WindowManager windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
Display display = windowManager.getDefaultDisplay();
Point screenSize = new Point();
display.getSize(screenSize);
// If the equator is no longer in view, FOV cannot be accurately calculated
StreetViewPanoramaOrientation orientation =
new StreetViewPanoramaOrientation(0, streetViewPanoramaCamera.bearing);
Point point = mStreetViewPanorama.orientationToPoint(orientation);
if (point.y < 0 || point.y > screenSize.y) {
return null;
}
Float lowerFovDegrees = getLowerFieldOfViewDegrees(
streetViewPanoramaCamera,
screenSize,
streetViewPanoramaCamera.bearing - 90f,
streetViewPanoramaCamera.bearing + 90f);
Float upperFovDegrees = getUpperFieldOfViewDegrees(
streetViewPanoramaCamera,
screenSize,
streetViewPanoramaCamera.bearing - 90f,
streetViewPanoramaCamera.bearing + 90f);
if ((lowerFovDegrees == null) || (upperFovDegrees == null)) return null;
float fieldOfViewDegrees = upperFovDegrees - lowerFovDegrees;
return fieldOfViewDegrees;
}
// Recursive binary search function
private Float getLowerFieldOfViewDegrees(StreetViewPanoramaCamera streetViewPanoramaCamera,
Point screenSize,
float startDegrees,
float endDegrees) {
StreetViewPanoramaOrientation orientation;
float midpointDegrees = (int) (startDegrees + ((endDegrees - startDegrees) / 2f));
orientation = new StreetViewPanoramaOrientation(0, midpointDegrees);
Point point = mStreetViewPanorama.orientationToPoint(orientation);
if ((point == null) || (point.x < 0)) {
if (endDegrees - midpointDegrees <= 1f) {
return endDegrees;
}
return getLowerFieldOfViewDegrees(
streetViewPanoramaCamera,
screenSize,
midpointDegrees,
endDegrees);
} else {
if (midpointDegrees - startDegrees <= 1f) {
return midpointDegrees;
}
return getLowerFieldOfViewDegrees(
streetViewPanoramaCamera,
screenSize,
startDegrees,
midpointDegrees);
}
}
// Recursive binary search function
private Float getUpperFieldOfViewDegrees(StreetViewPanoramaCamera streetViewPanoramaCamera,
Point screenSize,
float startDegrees,
float endDegrees) {
StreetViewPanoramaOrientation orientation;
float midpointDegrees = (int) (startDegrees + ((endDegrees - startDegrees) / 2f));
orientation = new StreetViewPanoramaOrientation(0, midpointDegrees);
Point point = mStreetViewPanorama.orientationToPoint(orientation);
if ((point == null) || (point.x > screenSize.x)) {
if (midpointDegrees - startDegrees <= 1f) {
return startDegrees;
}
return getUpperFieldOfViewDegrees(
streetViewPanoramaCamera,
screenSize,
startDegrees,
midpointDegrees);
} else {
if (endDegrees - midpointDegrees <= 1f) {
return midpointDegrees;
}
return getUpperFieldOfViewDegrees(
streetViewPanoramaCamera,
screenSize,
midpointDegrees,
endDegrees);
}
}
The granularity of the function above is to the nearest degree. You can modify it to give smaller granularity, but it will take longer to run. Since it can already cause graphical stutters I did not do this.
I hope this helps someone else as this seems to be a common question and I have not seen it solved for Android.
来源:https://stackoverflow.com/questions/32808818/google-streetview-conversion-between-fov-and-zoom