加载仅在屏幕上可见的标记 (Android) 的最佳方式?

The best way to load markers which are only visible on the screen (Android)?

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

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

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

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

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

希望对您有所帮助!

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

由于这不需要太精确,您可以使用 geohash 及其 lat/long 存储标记点。

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

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

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

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

更新:

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

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

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

接下来,我为 VisibleRegion class 设置了几个扩展,这是您从 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 
 */
@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,这段代码实际上告诉 "markers" 列表在 "markerFilter" 发生变化时进行更新。

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

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 class 看起来像这样:

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

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