在 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 还有其他初始化方法,通过使用这些方法,您可以为不同的桶提供不同的背景颜色,为不同的桶提供不同的背景图像等等。

如果需要任何进一步的帮助,请告诉我。