如何在不使用 CPU 的情况下显示大量 GMSPolylines?

How to display large amount of GMSPolylines without maxing out CPU usage?

我正在制作一个使用 NextBus API 和 Google 地图显示公交路线的应用。但是,我遇到了 CPU 用法的问题,我认为这是由地图上的 GMSPolylines 数量引起的。路线由一组折线显示,这些折线由 NextBus 为给定路线给出的点组成。当折线被添加到地图并且 GMSCamera 正在概览整条路线时,模拟器 (iPhone X) 上的 CPU 最大为 100%。然而,当放大路线的特定部分时,CPU 使用率下降到 ~2%。

地图截图:https://i.imgur.com/jLmN26e.png 性能:https://i.imgur.com/nUbIv5w.png

The NextBus API returns 路线信息包括特定巴士路径的路线。这是我正在处理的数据的一个小例子:

Route: {
    "path": [Path]
}

Path: {
    "points:" [Coordinate]
}

Coordinate: {
    "lat": Float,
    "lon": Float
}

这是我根据数据创建多段线的方法。总而言之,一条路线平均有约 700 个坐标分布在约 28 条折线(每个路径对象)上。请记住,我不会在一页上显示多条路线,我一次只显示一条路线。

func buildRoute(routePath: [Path?]) -> [GMSPolyline] {
    var polylines: [GMSPolyline] = []

    for path in routePath {
         let path = GMSMutablePath()
         guard let coords = path?.points else {continue}

         for coordinate in coords {
            // Safely unwrap latitude strings and convert them to doubles.
            guard let latStr = coordinate?.lat,
                  let lonStr = coordinate?.lon else {
                      continue
            }

            guard let latOne = Double(latStr),
                  let lonOne = Double(lonStr) else {
                      continue
            }

            // Create location coordinates.
            let pointCoordinatie = CLLocationCoordinate2D(latitude: latOne, longitude: lonOne)
            path.add(pointCoordinatie)
        }

        let line = GMSPolyline(path: path)
        line.strokeWidth = 6
        line.strokeColor = UIColor(red: 0/255, green: 104/255, blue: 139/255, alpha: 1.0)
        polylines.append(line)
    }

    return polylines
}

最后这是我将多段线添加到地图的方法:

fileprivate func buildRoute(routeConfig: RouteConfig?) {
    if let points = routeConfig?.route?.path {
        let polylines = RouteBuiler.shared.buildRoute(routePath: points)

        DispatchQueue.main.async {
            // Remove polylines from map if there are any.
            for line in self.currentRoute {
                line.map = nil
            }

            // Set new current route and add it to the map.
            self.currentRoute = polylines
            for line in self.currentRoute {
                line.map = self.mapView
            }
        }
    }
}

我构建折线的方式有问题吗?还是坐标太多了?

我 运行 正是这个问题。这是一个非常奇怪的错误——当你超过某个折线阈值时,CPU 突然固定到 100%。

我发现GMSPolygon没有这个问题。因此,我将所有 GMSPolyline 切换为 GMSPolygon。

为了获得正确的笔划宽度,我使用以下代码创建一个多边形,该多边形以给定的笔划宽度追踪多段线的轮廓。我的计算需要 LASwift 线性代数库。

https://github.com/AlexanderTar/LASwift

import CoreLocation
import LASwift
import GoogleMaps

struct Segment {
    let from: CLLocationCoordinate2D
    let to: CLLocationCoordinate2D
}

enum RightLeft {
    case right, left
}

// Offset the given path to the left or right by the given distance
func offsetPath(rightLeft: RightLeft, path: [CLLocationCoordinate2D], offset: Double) -> [CLLocationCoordinate2D] {
    var offsetPoints = [CLLocationCoordinate2D]()
    var prevSegment: Segment!

    for i in 0..<path.count {
        // Test if this is the last point
        if i == path.count-1 {
            if let to = prevSegment?.to {
                offsetPoints.append(to)
            }
            continue
        }

        let from = path[i]
        let to = path[i+1]

        // Skip duplicate points
        if from.latitude == to.latitude && from.longitude == to.longitude {
            continue
        }

        // Calculate the miter corner for the offset point
        let segmentAngle = -atan2(to.latitude - from.latitude, to.longitude - from.longitude)
        let sinA = sin(segmentAngle)
        let cosA = cos(segmentAngle)
        let rotate =
            Matrix([[cosA, -sinA, 0.0],
                    [sinA, cosA, 0.0],
                    [0.0, 0.0, 1.0]])
        let translate =
            Matrix([[1.0, 0.0, 0.0 ],
                    [0.0, 1.0, rightLeft == .left ? offset : -offset ],
                    [0.0, 0.0, 1.0]])
        let mat = inv(rotate) * translate * rotate

        let fromOff = mat * Matrix([[from.x], [from.y], [1.0]])
        let toOff = mat * Matrix([[to.x], [to.y], [1.0]])

        let offsetSegment = Segment(
            from: CLLocationCoordinate2D(latitude: fromOff[1,0], longitude: fromOff[0,0]),
            to: CLLocationCoordinate2D(latitude: toOff[1,0], longitude: toOff[0,0]))

        if prevSegment == nil {
            prevSegment = offsetSegment
            offsetPoints.append(offsetSegment.from)
            continue
        }

        // Calculate line intersection
        guard let intersection = getLineIntersection(line0: prevSegment, line1: offsetSegment, segment: false) else {
            prevSegment = offsetSegment
            continue
        }

        prevSegment = offsetSegment
        offsetPoints.append(intersection)
    }

    return offsetPoints
}

// Returns the intersection point if the line segments intersect, otherwise nil
func getLineIntersection(line0: Segment, line1: Segment, segment: Bool) -> CLLocationCoordinate2D? {
    return getLineIntersection(p0: line0.from, p1: line0.to, p2: line1.from, p3: line1.to, segment: segment)
}

// 
// Returns the intersection point if the line segments intersect, otherwise nil
func getLineIntersection(p0: CLLocationCoordinate2D, p1: CLLocationCoordinate2D, p2: CLLocationCoordinate2D, p3: CLLocationCoordinate2D, segment: Bool) -> CLLocationCoordinate2D? {
    let s1x = p1.longitude - p0.longitude
    let s1y = p1.latitude - p0.latitude
    let s2x = p3.longitude - p2.longitude
    let s2y = p3.latitude - p2.latitude

    let numerator = (s2x * (p0.latitude - p2.latitude) - s2y * (p0.longitude - p2.longitude))
    let denominator = (s1x * s2y - s2x * s1y)
    if denominator == 0.0 {
        return nil
    }
    let t =  numerator / denominator

    if segment {
        let s = (s1y * (p0.longitude - p2.longitude) + s1x * (p0.latitude - p2.latitude)) / (s1x * s2y - s2x * s1y)
        guard (s >= 0 && s <= 1 && t >= 0 && t <= 1) else {
            return nil
        }
    }

    return CLLocationCoordinate2D(latitude: p0.latitude + (t  * s1y), longitude: p0.longitude + (t * s1x))
}


// The path from NextBus
let path: CLLocationCoordinate2D = pathFromNextBus()

// The desired width of the polyline
let strokeWidth: Double = desiredPolylineWidth()

let polygon: GMSPolygon
do {
    let polygonPath = GMSMutablePath()
    let w = strokeWidth / 2.0
    for point in offsetPath(rightLeft: .left, path: route.offsetPath, offset: w) {
        polygonPath.add(CLLocationCoordinate2D(latitude: point.latitude, longitude: point.longitude))
    }
    for point in offsetPath(rightLeft: .right, path: route.offsetPath, offset: w).reversed() {
        polygonPath.add(CLLocationCoordinate2D(latitude: point.latitude, longitude: point.longitude))
    }
    polygon = GMSPolygon(path: polygonPath)
    polygon.strokeWidth = 0.0
}