在 iOS 上使用 GMUClusterRenderer(Google 地图聚类)的 scrolling/zooming 体验非常慢
Very slow scrolling/zooming experience with GMUClusterRenderer (Google Maps Clustering) on iOS
我会尝试解释我的问题,以及我到目前为止所做的事情。
简介:
我正在使用 Google 地图中的 iOS Utils Library 以便在地图上显示大约 300 个标记。
用于聚类的算法是GMUNonHierarchicalDistanceBasedAlgorithm
。
基本上,我们的用户可以通过 window 将他们观察到的天气 发送给我们,这样我们就可以显示世界各地的实时天气。
它使我们能够改进and/or调整天气预报。
但是我的scrolling/zooming经历一点都不顺利。顺便说一句,我正在用 iPhone X ...
测试它
让我们直奔主题:
下面是我如何配置 ClusterManager
private func configureCluster(array: [Observation]) -> Void {
let iconGenerator = GMUDefaultClusterIconGenerator()
let algorithm = GMUNonHierarchicalDistanceBasedAlgorithm()
let renderer = GMUDefaultClusterRenderer(mapView: mapView,
clusterIconGenerator: iconGenerator)
renderer.delegate = self
clusterManager = GMUClusterManager(map: mapView, algorithm: algorithm,
renderer: renderer)
clusterManager.add(array)
clusterManager.cluster()
clusterManager.setDelegate(self, mapDelegate: self)
}
这是我的 Observation
class,我尽量保持简单 :
class Observation : NSObject, GMUClusterItem {
static var ICON_SIZE = 30
let timestamp: Double
let idObs: String
let position: CLLocationCoordinate2D
let idPicto: [Int]
let token: String
let comment: String
let altitude: Double
init(timestamp: Double, idObs: String, coordinate: CLLocationCoordinate2D, idPicto: [Int], token: String, comment: String, altitude: Double) {
self.timestamp = timestamp
self.idObs = idObs
self.position = coordinate
self.idPicto = idPicto
self.token = token
self.comment = comment
self.altitude = altitude
}
}
最后,渲染的委托方法:
func renderer(_ renderer: GMUClusterRenderer, willRenderMarker marker: GMSMarker) {
if let cluster = marker.userData as? GMUCluster {
if let listObs = cluster.items as? [Observation] {
if listObs.count > 1 {
let sortedObs = listObs.sorted(by: { [=15=].timestamp > .timestamp })
if let mostRecentObs = sortedObs.first {
DispatchQueue.main.async {
self.setIconViewForMarker(marker: marker, obs: mostRecentObs)
}
}
} else {
if let obs = listObs.last {
DispatchQueue.main.async {
self.setIconViewForMarker(marker: marker, obs: obs)
}
}
}
}
}
}
用户只能发送一个观测值,但该观测值可以由各种天气现象(如云 + 雨 + 风)组成,如果需要,也可以只发送下雨。
区分一下,如果只有1个现象,直接设置marker.iconView
属性
另一方面,如果是对多个现象的观察,我将创建一个视图,其中包含表示现象的所有图像。
func setIconViewForMarker(marker: GMSMarker, obs: Observation) -> Void {
let isYourObs = Observation.isOwnObservation(id: obs.idObs) ? true : false
if isYourObs {
marker.iconView = Observation.viewForPhenomenomArray(ids: obs.idPicto, isYourObs: isYourObs)
} else {
// Observation with more than 1 phenomenom
if obs.idPicto.count > 1 {
marker.iconView = Observation.viewForPhenomenomArray(ids: obs.idPicto, isYourObs: isYourObs)
// Observation with only 1 phenomenom
} else if obs.idPicto.count == 1 {
if let id = obs.idPicto.last {
marker.iconView = Observation.setImageForPhenomenom(id: id)
}
}
}
}
最后一段代码,向您展示我如何构建此自定义视图(我想我的问题可能出在这里)
class func viewForPhenomenomArray(ids: [Int], isYourObs: Bool) -> UIView {
let popupView = UIView()
popupView.frame = CGRect.init(x: 0, y: 0, width: (ICON_SIZE * ids.count) + ((ids.count + 1) * 5) , height: ICON_SIZE)
if (isYourObs) {
popupView.backgroundColor = UIColor(red:0.25, green:0.61, blue:0.20, alpha:1)
} else {
popupView.backgroundColor = UIColor(red:0.00, green:0.31, blue:0.57, alpha:1)
}
popupView.layer.cornerRadius = 12
for (index, element) in ids.enumerated() {
let imageView = UIImageView(image: Observation.getPictoFromID(id: element))
imageView.frame = CGRect(x: ((index + 1) * 5) + index * ICON_SIZE, y: 0, width: ICON_SIZE, height: ICON_SIZE)
popupView.addSubview(imageView)
}
return popupView
}
我也尝试了非常小的图像,以了解问题是否来自在地图上渲染大量 PNG,但说真的,它是一个 iPhone X,它应该能够渲染一些简单的天气地图上的图标。
你觉得我做错了什么吗?还是 Google Maps SDK 中的已知问题? (我看过它固定在 30 fps)
您认为在地图上渲染大量图像(如 marker.image)需要那么多 GPU 吗?达到完全无法接受的程度?
如果你有任何建议,我都会采纳。
我遇到了同样的问题。经过大量调试甚至检查 google 的代码后,我得出的结论是,问题来自 GMUDefaultClusterIconGenerator
。此 class 正在运行时为您显示的给定簇大小创建图像。因此,当您放大或缩小地图时,集群大小将会更新,并且此 class 会为新数字创建新图像(即使它会保留图像缓存,如果重复相同的数字)。
所以,我找到的解决方案是使用 buckets
。看到这个新术语,您会感到惊讶。让我通过一个简单的例子来解释桶的概念。
假设您将存储桶大小设置为 10、20、50、100、200、500、1000。
- 现在,如果您的集群是 3,那么它将显示 3。
- 如果簇大小 = 8,则显示 = 8。
- 如果簇大小 = 16,则显示 = 10+。
- 如果簇大小 = 22,则显示 = 20+。
- 如果簇大小 = 48,则显示 = 20+。
- 如果簇大小 = 91,则显示 = 50+。
- 如果簇大小 = 177,则显示 = 100+。
- 如果集群大小 = 502,则显示 = 500+。
- 如果簇大小 = 1200004,则显示 = 1000+。
现在在这里,对于任何簇大小,要渲染的标记图像将来自 1、2、3、4、5、6、7、8、9、10+、20+、50 +、100+、200+、500+、1000+。因为它缓存图像,所以这些图像将被重用。因此,它用于创建新图像的时间+cpu 减少了(只需要创建很少的图像)。
关于水桶,您现在一定已经明白了。因为,如果集群的数量非常少,那么集群的大小很重要,但如果增加,那么桶的大小就足以了解集群的大小。
现在,问题是如何实现这一点。
其实GMUDefaultClusterIconGenerator
class已经实现了这个功能,你只需要把它的初始化改成这样:
let iconGenerator = GMUDefaultClusterIconGenerator(buckets: [ 10, 20, 50, 100, 200, 500, 1000])
GMUDefaultClusterIconGenerator
class 还有其他初始化方法,通过使用这些方法,您可以为不同的桶提供不同的背景颜色,为不同的桶提供不同的背景图像等等。
如果需要任何进一步的帮助,请告诉我。
我会尝试解释我的问题,以及我到目前为止所做的事情。
简介:
我正在使用 Google 地图中的 iOS Utils Library 以便在地图上显示大约 300 个标记。
用于聚类的算法是GMUNonHierarchicalDistanceBasedAlgorithm
。
基本上,我们的用户可以通过 window 将他们观察到的天气 发送给我们,这样我们就可以显示世界各地的实时天气。
它使我们能够改进and/or调整天气预报。
但是我的scrolling/zooming经历一点都不顺利。顺便说一句,我正在用 iPhone X ...
测试它让我们直奔主题:
下面是我如何配置 ClusterManager
private func configureCluster(array: [Observation]) -> Void {
let iconGenerator = GMUDefaultClusterIconGenerator()
let algorithm = GMUNonHierarchicalDistanceBasedAlgorithm()
let renderer = GMUDefaultClusterRenderer(mapView: mapView,
clusterIconGenerator: iconGenerator)
renderer.delegate = self
clusterManager = GMUClusterManager(map: mapView, algorithm: algorithm,
renderer: renderer)
clusterManager.add(array)
clusterManager.cluster()
clusterManager.setDelegate(self, mapDelegate: self)
}
这是我的 Observation
class,我尽量保持简单 :
class Observation : NSObject, GMUClusterItem {
static var ICON_SIZE = 30
let timestamp: Double
let idObs: String
let position: CLLocationCoordinate2D
let idPicto: [Int]
let token: String
let comment: String
let altitude: Double
init(timestamp: Double, idObs: String, coordinate: CLLocationCoordinate2D, idPicto: [Int], token: String, comment: String, altitude: Double) {
self.timestamp = timestamp
self.idObs = idObs
self.position = coordinate
self.idPicto = idPicto
self.token = token
self.comment = comment
self.altitude = altitude
}
}
最后,渲染的委托方法:
func renderer(_ renderer: GMUClusterRenderer, willRenderMarker marker: GMSMarker) {
if let cluster = marker.userData as? GMUCluster {
if let listObs = cluster.items as? [Observation] {
if listObs.count > 1 {
let sortedObs = listObs.sorted(by: { [=15=].timestamp > .timestamp })
if let mostRecentObs = sortedObs.first {
DispatchQueue.main.async {
self.setIconViewForMarker(marker: marker, obs: mostRecentObs)
}
}
} else {
if let obs = listObs.last {
DispatchQueue.main.async {
self.setIconViewForMarker(marker: marker, obs: obs)
}
}
}
}
}
}
用户只能发送一个观测值,但该观测值可以由各种天气现象(如云 + 雨 + 风)组成,如果需要,也可以只发送下雨。
区分一下,如果只有1个现象,直接设置marker.iconView
属性
另一方面,如果是对多个现象的观察,我将创建一个视图,其中包含表示现象的所有图像。
func setIconViewForMarker(marker: GMSMarker, obs: Observation) -> Void {
let isYourObs = Observation.isOwnObservation(id: obs.idObs) ? true : false
if isYourObs {
marker.iconView = Observation.viewForPhenomenomArray(ids: obs.idPicto, isYourObs: isYourObs)
} else {
// Observation with more than 1 phenomenom
if obs.idPicto.count > 1 {
marker.iconView = Observation.viewForPhenomenomArray(ids: obs.idPicto, isYourObs: isYourObs)
// Observation with only 1 phenomenom
} else if obs.idPicto.count == 1 {
if let id = obs.idPicto.last {
marker.iconView = Observation.setImageForPhenomenom(id: id)
}
}
}
}
最后一段代码,向您展示我如何构建此自定义视图(我想我的问题可能出在这里)
class func viewForPhenomenomArray(ids: [Int], isYourObs: Bool) -> UIView {
let popupView = UIView()
popupView.frame = CGRect.init(x: 0, y: 0, width: (ICON_SIZE * ids.count) + ((ids.count + 1) * 5) , height: ICON_SIZE)
if (isYourObs) {
popupView.backgroundColor = UIColor(red:0.25, green:0.61, blue:0.20, alpha:1)
} else {
popupView.backgroundColor = UIColor(red:0.00, green:0.31, blue:0.57, alpha:1)
}
popupView.layer.cornerRadius = 12
for (index, element) in ids.enumerated() {
let imageView = UIImageView(image: Observation.getPictoFromID(id: element))
imageView.frame = CGRect(x: ((index + 1) * 5) + index * ICON_SIZE, y: 0, width: ICON_SIZE, height: ICON_SIZE)
popupView.addSubview(imageView)
}
return popupView
}
我也尝试了非常小的图像,以了解问题是否来自在地图上渲染大量 PNG,但说真的,它是一个 iPhone X,它应该能够渲染一些简单的天气地图上的图标。
你觉得我做错了什么吗?还是 Google Maps SDK 中的已知问题? (我看过它固定在 30 fps)
您认为在地图上渲染大量图像(如 marker.image)需要那么多 GPU 吗?达到完全无法接受的程度?
如果你有任何建议,我都会采纳。
我遇到了同样的问题。经过大量调试甚至检查 google 的代码后,我得出的结论是,问题来自 GMUDefaultClusterIconGenerator
。此 class 正在运行时为您显示的给定簇大小创建图像。因此,当您放大或缩小地图时,集群大小将会更新,并且此 class 会为新数字创建新图像(即使它会保留图像缓存,如果重复相同的数字)。
所以,我找到的解决方案是使用 buckets
。看到这个新术语,您会感到惊讶。让我通过一个简单的例子来解释桶的概念。
假设您将存储桶大小设置为 10、20、50、100、200、500、1000。
- 现在,如果您的集群是 3,那么它将显示 3。
- 如果簇大小 = 8,则显示 = 8。
- 如果簇大小 = 16,则显示 = 10+。
- 如果簇大小 = 22,则显示 = 20+。
- 如果簇大小 = 48,则显示 = 20+。
- 如果簇大小 = 91,则显示 = 50+。
- 如果簇大小 = 177,则显示 = 100+。
- 如果集群大小 = 502,则显示 = 500+。
- 如果簇大小 = 1200004,则显示 = 1000+。
现在在这里,对于任何簇大小,要渲染的标记图像将来自 1、2、3、4、5、6、7、8、9、10+、20+、50 +、100+、200+、500+、1000+。因为它缓存图像,所以这些图像将被重用。因此,它用于创建新图像的时间+cpu 减少了(只需要创建很少的图像)。
关于水桶,您现在一定已经明白了。因为,如果集群的数量非常少,那么集群的大小很重要,但如果增加,那么桶的大小就足以了解集群的大小。
现在,问题是如何实现这一点。
其实GMUDefaultClusterIconGenerator
class已经实现了这个功能,你只需要把它的初始化改成这样:
let iconGenerator = GMUDefaultClusterIconGenerator(buckets: [ 10, 20, 50, 100, 200, 500, 1000])
GMUDefaultClusterIconGenerator
class 还有其他初始化方法,通过使用这些方法,您可以为不同的桶提供不同的背景颜色,为不同的桶提供不同的背景图像等等。
如果需要任何进一步的帮助,请告诉我。