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

时间:2015-03-21 13:28:32

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

我有一个数据库,它存储了很多标记,我正在Android上开发一个包含Google Maps Android V2的应用程序。

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

3 个答案:

答案 0 :(得分:0)

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

答案 1 :(得分:0)

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

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

希望这对您有帮助!

答案 2 :(得分:0)

所以真正的问题是您想要调整查询,以过滤掉您在地图上看不到的内容,从而不必加载或遍历它们。

由于这不必具有很高的精度,因此可以使用geohash以及其经/纬度存储标记点。

Geohash确实很容易在查询中使用以限制记录,因为它是坐标的顺序字符串表示形式。

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

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

Geohash并不是那么精确,例如,在3到2之间,可见半径从78km跃升到630km,但是对于大多数合理的地图相机缩放级别而言,这足以限制您要查询的点数。

更新:

我是来这里寻找类似问题的解决方案的,在写完此书之后,就走了并实施了它。这是第一次破解,但是我想我会与有兴趣尝试该代码的人共享该代码。

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

第一步是选择一个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”发生更改,该代码就会告诉“标记”列表进行更新。

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

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

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