【问题标题】:The best way to load markers which are only visible on the screen (Android)?加载仅在屏幕上可见的标记的最佳方法(Android)?
【发布时间】:2015-05-24 20:23:39
【问题描述】:

我有一个存储大量标记的数据库,我正在 Android 上开发一个应用程序,其中包括 Google Maps Android V2。

我一直在寻找一种方法来加载并非存储在此数据库中的所有标记,但只有那些在屏幕上可见但没有找到,请问我该怎么做?

【问题讨论】:

    标签: android maps google-maps-markers google-maps-android-api-2


    【解决方案1】:

    我认为没有办法批量执行此操作,您必须一次对每个标记进行操作。在屏幕上获取地图区域的边界:Android Google Maps get Boundary Co-ordinates,然后使用该LatLngBounds 检查每个标记的LatLng,看看是否要显示它。

    【讨论】:

    • 谢谢,这是个好主意,但是如果我的数据库中有超过 10k 条记录,那将需要很长时间才能完成,不是吗?
    • 是的,这可能需要一段时间,您肯定希望在后台线程中执行此操作。我不知道有什么比这更快的方法。
    • @PHP5 找到解决方案了吗?
    【解决方案2】:

    有一种方法可以找到可见区域上的所有标记位置。为此,您必须遍历所有 LatLng 值。

    for(int i=0; i < latLngListsize(); i++)
      mGoogleMap.getProjection().getVisibleRegion().latLngBounds.contains(new LatLng(latLngListsize.get(i).getLatitude(), latLngListsize.get(i).getLongitude()));
    

    希望对您有所帮助!

    【讨论】:

      【解决方案3】:

      所以真正的问题是你想要调整你的查询,过滤掉你在地图上看不到的那些,这样你就不必加载它们或迭代它们。

      由于这不需要非常精确,您可以使用geohash 存储您的标记点,以及它们的纬度/经度。

      Geohash 非常容易在查询中用于限制记录,因为它是坐标的顺序字符串表示。

      例如,如果您有两个标记记录,其 geohash 为 2j3l45d6 和 2j3lyt76,并且该点周围的半径为 60 公里,那么您可以设置如下查询:

      SELECT * FROM points WHERE geohash LIKE "2j3%"
      

      Geohash 并不是那么精确,例如,在精度 3 和 2 之间,可见半径从 78 公里跃升至 630 公里,但对于最合理的地图相机缩放级别而言,它足以限制您查询的点数。

      更新:

      我来这里是为了寻找类似问题的解决方案,写完这篇文章后,就离开并实施了。这是第一次破解,但我想我会将代码分享给任何有兴趣尝试的人。

      这里有一点额外的代码,比如能够以米为单位指定返回半径,这不是严格要求的,但我在其他地方使用。

      第一步是选择一个 GeoHash 库。由于我使用的是 Kotlin,因此我选择了 Android-Kotlin-Geohash,但几乎任何库都可以使用。

      接下来我为 VisibleRegion 类设置了几个扩展,这是您从 Android 上的 GoogleMap 对象返回的内容:

      /**
       * Returns a geohash based on the point, with precision great enough to cover the radius.
       */
      fun VisibleRegion.visibleRadiusGeohash(point: LatLng): GeoHash? {
          return GeoHash(
              lat = point.latitude,
              lon = point.longitude,
              charsCount = when (calculateVisibleRadius(UnitOfMeasure.KILOMETER)) {
                  in 0.0..0.019 -> 8
                  in 0.02..0.076 -> 7
                  in 0.077..0.61 -> 6
                  in 0.62..2.4 -> 5
                  in 2.5..20.0 -> 4
                  in 21.0..78.0 -> 3
                  in 79.0..630.0 -> 2
                  else -> 1 // 2,500km and greater
              }
          )
      }
      
      /**
       * @param units one of UnitOfMeasure.KILOMETER or UnitOfMeasure.METER.
       * Any other unit will throw an IllegalArgumentException.
       *
       * This code adapted from https://stackoverflow.com/a/49413106/300236
       */
      @Throws(IllegalArgumentException::class)
      fun VisibleRegion.calculateVisibleRadius(units: UnitOfMeasure): Double {
          val diameter = FloatArray(1)
          Location.distanceBetween(
              (farLeft.latitude + nearLeft.latitude) / 2,
              farLeft.longitude,
              (farRight.latitude + nearRight.latitude) / 2,
              farRight.longitude,
              diameter
          )
          // visible radius in meters is the diameter / 2, and /1000 for Km:
          return when (units) {
              UnitOfMeasure.KILOMETER -> (diameter[0] / 2 / 1000).toDouble()
              UnitOfMeasure.METER -> (diameter[0] / 2).toDouble()
              else -> throw IllegalArgumentException("Valid units are KILOMETER and METER only")
          }
      }
      

      由于我使用的是 MVVM,因此我设置了一个具有一些可观察属性的视图模型。一个是我要使用的过滤器,另一个是我要在地图上绘制的点列表。请注意,null 对于过滤器来说是一个完全有效的值,并且会导致所有标记都被加载:

          val markerFilter: MutableLiveData<String?> = MutableLiveData<String?>(null)
      
          val markers: LiveData<List<Mark>> =
              Transformations.switchMap(markerFilter) { geoHash ->
                  liveData(Dispatchers.IO) {
                      emitSource(markRepo.loadMarkerList(geohash = geoHash))
                  }
              }
      

      如果您不熟悉 ViewModel,则此代码实质上是在“markerFilter”更改时告诉“markers”列表进行更新。

      接下来,我需要知道相机何时移动以及地图视口何时发生变化,因此在我的地图视图片段中,我会监听地图移动事件。

      override fun onCameraMove() {
              mapView?.let { map ->
                  val visibleRegion: VisibleRegion = map.projection.visibleRegion
                  val centreOfVisibleRegion = visibleRegion.latLngBounds.center
                  val geohash = visibleRegion.visibleRadiusGeohash(centreOfVisibleRegion)
                  model. markerFilter.postValue(geohash.toString())
              }
      
          }
      

      (查看这段代码,我可以看到一个很好的重构,它可以简化这个,但在我回去做之前它会起作用)

      ...当然,我需要观察我的标记列表,以便在列表更改时将它们添加到地图中:

      model.markers.observe(viewLifecycleOwner, Observer { list ->
                      list?.let {addMarkersToMap(it)}
      })
      

      最后一点是设置存储库来运行查询,所以我的 MarkerRepository 类如下所示:

      suspend fun loadMarkerList(geohash: String?): LiveData<List<Mark>> {
              return withContext(io) {
                  dao.loadMarkerList(geohash ?: "")
              }
          }
      

      请注意,我没有传递空过滤器值,如果它为空,我将其设置为空字符串,因此生成的 LIKE 条件将起作用。

      ...我的 MarkerDao 看起来像:

      @Query("SELECT * FROM marker WHERE geohash LIKE  :filter || '%' ORDER BY geohash")
          fun loadMarkerList(filter: String): LiveData<List<Mark>>
      

      希望这能给其他人提供足够的解决方案,让他们自己尝试。

      【讨论】:

        猜你喜欢
        • 2011-03-26
        • 2019-12-24
        • 1970-01-01
        • 2021-03-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-09-30
        • 2010-10-03
        相关资源
        最近更新 更多