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



我有一个数据库,里面存储了很多标记,我正在安卓系统上开发一个应用程序,其中包括谷歌地图安卓V2。

我一直在搜索一种方法来加载存储在这个数据库中的所有标记,但只有那些在屏幕上可见但没有找到的标记,我该怎么做?

我认为没有办法批量完成这项工作,您必须一次对每个标记进行一个操作。在屏幕上获取地图区域的边界:Android谷歌地图获取边界坐标,然后使用该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()));

希望这对你有帮助!

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

由于这不需要太高的精度,您可以使用地理哈希存储标记点以及它们的lat/long。

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

例如,如果你有两个地理哈希为2j3l45d6和2j3lyt76的标记记录,并且你在该点周围的半径是60km,那么你可以设置这样的查询:

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"更改时,这段代码基本上告诉"marks"列表要更新。

接下来,我需要知道相机何时移动,地图视口何时更改,因此在我的地图视图片段中,我会侦听地图移动事件。

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

最后一点是设置Repository来运行查询,所以我的MarkerRepository类看起来像这样:

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

请注意,我不会传递null过滤器值,如果它为null,我会将其设置为空字符串,因此生成的LIKE标准将起作用。

我的MarkerDao看起来像:

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

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

最新更新