【问题标题】:Google Streetview: Conversion between FOV and Zoom谷歌街景:FOV和Zoom之间的转换
【发布时间】:2015-09-27 14:10:58
【问题描述】:

有谁知道我可以使用一个公式将 Google Maps Android API 中的缩放:https://developers.google.com/maps/documentation/android-api/streetview 转换为 Google StreetView Image API 中使用的 FOV:https://developers.google.com/maps/documentation/streetview/intro

我在这里找到了几个旧公式:

  1. https://groups.google.com/forum/#!topic/google-maps-js-api-v3/uqKfg0ZBhWc
  2. https://groups.google.com/forum/#!msg/google-maps-image-apis/O_Odb0A7_0c/Q74cHCoRuscJ

然后我对它们进行了测试,当我在我的 Android 手机上缩放相机时,将计算 FOV:

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) ) ;

}

在 android 上缩放 0.0 时,我们返回以下 FOV 值:

09-27 15:53:52.322 E/Pano:轴承:228.28955 倾斜:14.516191 缩放: 0.0 FOV1:123.0 FOV2:120.0 FOV3:180.0

由于 FOV 的最大值为 120,FOV2 的公式起初看起来很有希望,但当我放大 2 倍时,它给出的值为 76.8,与实际相差甚远:

09-27 16:01:48.235 E/Pano:轴承:223.11241 倾斜:1.852709 缩放:2.0 FOV1:53.7432 FOV2:76.8 FOV3:45.0

这是我手机放大2倍后的图片:

这是从 Google Streetview Image API 在 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) 下载的图像:

这是从 Google Streetview Image API 在 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) 下载的图像:

我得到的最接近的是,如果我制作了 FOV 26,但那是我的猜测,而不是使用公式 - 下图是 FOV 26:

【问题讨论】:

    标签: android google-maps google-street-view


    【解决方案1】:

    我最后不得不回归这条线来得到指数公式。

    我使用了这个网站并手动输入了 FOV 和缩放值的数据点组合,这些数据点可以很好地协同工作:http://www.had2know.com/academics/regression-calculator-statistics-best-fit.html

    我的数据点是:

    FOV(FOV 旁边括号中的 ZOOM)

    120 (0)

    50 (1)

    45 (1.2)

    37 (1.5)

    26 (2)

    69 (0.5)

    78 (0.3)

    31 (1.9)

    要获得更接近的公式,您可以不断添加更多“适合”的数据点,但我没有时间 - 任何想要完善公式的人都可以尝试。

    我得到了下面的公式,其中 Y 是 FOV,X 是缩放。

    Y = 103.7587(0.5051^X)

    相关性 = -0.9882

    【讨论】:

      【解决方案2】:

      您可以使用以下公式(JavaScript 中的代码):

      • fov转换为zoom

        zoom = Math.log(180/fov)/(Math.log(2))

      • zoom转换为fov

        fov = 180 / Math.pow(2, zoom)

      【讨论】:

      • 此公式不适用于 Android。在 Android 中,特定缩放的 FOV 与 Web 非常不同。
      【解决方案3】:

      谷歌搜索这个答案我只能找到网络公式。 Web 是不同的,因为首先,文档中有一个 Zoom vs FOV 图表: https://developers.google.com/maps/documentation/javascript/streetview#TilingPanoramas

      因此可以想出一个公式。 Android 文档没有这样的表格,人们用于 web 的公式不适用于 Android(甚至不能关闭)。

      不过有一些好消息,Android API 中提供了这个功能: StreetViewPanorama.orientationToPoint(StreetViewPanoramaOrientation orientation)

      这个函数允许我们提供一个方向,它会返回屏幕上那个方向所在的一个点。该函数的文档说,如果该方向不在屏幕上,它将返回 null,但实际上我发现它会在屏幕的可视区域之外给出一个点;但是,如果我们知道屏幕尺寸,我们可以检查该点是否有效。通过循环 180 度,我们可以知道该范围有多少是可见的,因此是视野。

      警告:此功能效率低下。仅在缩放已更改时才调用!

      注意:下面的示例代码计算水平视野。要计算垂直视野,您需要进行一些更改。例如,将 point.x 更改为 point.y,将 screenSize.x 的所有实例更改为 screenSize.y,将 streetViewPanoramaCamera.bearing 的所有实例更改为 streetViewPanoramaCamera.tilt,并更改以下所有实例:

      orientation = new StreetViewPanoramaOrientation(streetViewPanoramaCamera.tilt, angleCheck);
      

      到:

      orientation = new StreetViewPanoramaOrientation(angleCheck, streetViewPanoramaCamera.bearing);
      

      这是一种简单的方法,我们只是从 -90 度循环到 +90 度,然后查看 180 度中有多少在可视区域中(注意:这仅在倾斜为 0 度时才有效,即用户没有向上或向下看):

      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;
      }
      

      但是,orientationToPoint() 很昂贵,最多可以调用 180 次。我们可以使用二分搜索来提高效率(尽管更复杂),并将其减少到大约 13 次调用。在这种情况下,我使用递归函数来执行二进制搜索。还有一个限制是水平赤道必须在视野内,否则无法准确计算 FOV;在这种情况下,它返回 null。只要用户没有完全向下或向上倾斜,这并不是真正的问题,但如果他们这样做了,FOV 将返回 null 并且您需要隐藏任何依赖于 FOV 准确的 UI 元素(或者它们不会在正确的位置)。

      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);
          }
      }
      

      上面函数的粒度是最接近的度数。您可以对其进行修改以提供更小的粒度,但运行时间会更长。由于它已经可能导致图形卡顿,因此我没有这样做。

      我希望这对其他人有所帮助,因为这似乎是一个常见问题,我还没有看到它在 Android 上得到解决。

      【讨论】:

        【解决方案4】:

        我找到了一个更好的方法,下面是 JavaScript:

        function convertFovToZoom(fov: number) {
          return 1 - Math.log(Math.tan((fov * Math.PI) / 360)) / Math.log(2);
        }
         function convertZoomToFov(zoom: number) {
          return 180 / (2 ** zoom);
        }
        

        为我在移动设备上运行的 React Native 工作。但目前只在 iOS 上进行测试。

        致谢作者:https://ramblings.mcpher.com/ephemeral-exchange/playing-around-with-maps-and-streetview/convert-field-of-view-to-zoom-and-back-again/

        【讨论】:

          猜你喜欢
          • 2016-05-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-09-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多