ios - iOS 上 GMUClusterRenderer(谷歌地图集群)的滚动/缩放体验非常慢

标签 ios swift google-maps hierarchical-clustering

我将尝试解释我的问题,以及我到目前为止所做的事情。

简介:

我正在使用 iOS Utils Library来自 Google map ,以便在 map 上显示大约 300 个标记。

用于聚类的算法是 GMUNonHierarchicalDistanceBasedAlgorithm

基本上,我们的用户可以通过他们的窗口向我们发送他们观察到的天气,这样我们就可以显示世界各地的实时天气。

它使我们能够改进和/或调整天气预报。

但我的滚动/缩放体验一点也不流畅。顺便说一句,我正在用 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 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
    }
}

最后,渲染的委托(delegate)方法:

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: { $0.timestamp > $1.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)
                        }
                    }
                }
            }
        }
    }

用户只能发送一个观测值,但该观测值可以由各种天气现象(如云 + 雨 + 风)组成,如果需要,也可以只发送下雨。

区分它们,如果只有一种现象,则直接设置marker.iconView属性。

另一方面,如果是对多个现象的观察,我将创建一个 View ,其中包含表示现象的所有图像。

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

最后一段代码,向您展示我是如何构建这个自定义 View 的(我想我的问题可能就在这里)

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
    }

我也尝试过使用非常小的图像,以了解问题是否来自在 map 上渲染大量 PNG,但说真的,它是 iPhone X,它应该能够在 map 上渲染一些简单的天气图标。

你认为我做错了什么吗?还是 Google Maps SDK 中的已知问题? (我看过它固定在 30 fps)

您认为在 map 上渲染大量图像(如 marker.image)需要那么多 GPU 吗?达到完全无法接受体验的地步?

如果你有任何建议,我都会采纳。

最佳答案

我遇到了同样的问题。经过大量调试甚至检查谷歌代码后,我得出的结论是,问题出自 GMUDefaultClusterIconGenerator。此类在运行时为您显示的给定集群大小创建图像。因此,当您放大或缩小 map 时,集群大小将会更新,并且此类会为新数字创建新图像(即使它会保留图像缓存,如果重复相同的数字)。

所以,我找到的解决方案是使用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 类已经实现了这个功能,你只需要把它的初始化改成这样:

let iconGenerator = GMUDefaultClusterIconGenerator(buckets: [ 10, 20, 50, 100, 200, 500, 1000])

GMUDefaultClusterIconGenerator 类还有其他初始化方法,通过使用这些方法,您可以为不同的桶提供不同的背景颜色,为不同的桶提供不同的背景图像等等。

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

关于ios - iOS 上 GMUClusterRenderer(谷歌地图集群)的滚动/缩放体验非常慢,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54145142/

相关文章:

android - ios 闹钟功能受限?

ios - Apple 推送通知中的 SSL 证书

ios - 如何显示多个本地通知?

ios - 从 UITableviewCell 中的按钮打开的下拉菜单 UIView 在单元格边界外不可单击

android - 在 Android 中绘制(过滤)100k+ 点到 MapView

ios - UICollectionView 页眉和页脚 View

iphone - 更改 UILabel 背景颜色

ios - Carthage Bootstrap 构建失败 "exit code 1"

javascript - Google Maps API 上的坐标完全相同

android - 更改标题语言 PlacePicker