【发布时间】:2015-05-24 20:23:39
【问题描述】:
我有一个存储大量标记的数据库,我正在 Android 上开发一个应用程序,其中包括 Google Maps Android V2。
我一直在寻找一种方法来加载并非存储在此数据库中的所有标记,但只有那些在屏幕上可见但没有找到,请问我该怎么做?
【问题讨论】:
标签: android maps google-maps-markers google-maps-android-api-2
我有一个存储大量标记的数据库,我正在 Android 上开发一个应用程序,其中包括 Google Maps Android V2。
我一直在寻找一种方法来加载并非存储在此数据库中的所有标记,但只有那些在屏幕上可见但没有找到,请问我该怎么做?
【问题讨论】:
标签: android maps google-maps-markers google-maps-android-api-2
我认为没有办法批量执行此操作,您必须一次对每个标记进行操作。在屏幕上获取地图区域的边界: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 存储您的标记点,以及它们的纬度/经度。
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>>
希望这能给其他人提供足够的解决方案,让他们自己尝试。
【讨论】: