如何在 IOS 中弯曲折线(谷歌地图)?

Posted

技术标签:

【中文标题】如何在 IOS 中弯曲折线(谷歌地图)?【英文标题】:How to Curve a polyline (Google Maps) in IOS? 【发布时间】:2018-09-11 11:27:39 【问题描述】:

我想在两点之间弯曲折线。

android 在 SphericalUtil 类的帮助下做到这一点。 但在 ios 中我不知道该怎么做。

对于 Android ,这是一个参考链接: Draw ARC Polyline in Google Map

我希望 iOS 也一样。

【问题讨论】:

UIBezierPath?! 您可以使用UIBezierPath 或第三方如github.com/ivan114/IVBezierPathRenderer.. 我可以使用 UIBezierPath 绘制路径,但它可以在地图上的两个纬度经度点之间使用吗? 【参考方案1】:

聚会迟到了,但为将来遇到此问题的其他人添加解决方案:

首先:使用UIBezierPath 绘制曲线

关注UIBezierPath 的扩展。使用在CGPoint.controlpoint(_, _) 中计算的垂直控制点将每对点绘制为四边形曲线。

使用tension 值来改变曲线的锐度。 tension2 生成接近四分之一圆的东西。

extension UIBezierPath 

    static func from(_ points: [CGPoint], tension: CGFloat) -> UIBezierPath 
        let path = UIBezierPath()

        guard let first = points.first, points.count > 1 else 
            return path
        

        path.move(to: first)

        points
            .pairwise()
            .dropFirst()
            .forEach  a, b in
                path.addQuadCurve(
                    to: b,
                    controlPoint: .controlpoint(a!, b, tension: tension)
                )
            

        return path
    


extension CGPoint 

    static func controlpoint(_ l: CGPoint, _ r: CGPoint, tension: CGFloat = 2) -> CGPoint 
        midpoint(l, r) + (perpendicular(l, r) / tension)
    

    static func midpoint(_ l: CGPoint, _ r: CGPoint) -> CGPoint 
        (l + r) / 2
    

    static func perpendicular(_ l: CGPoint, _ r: CGPoint) -> CGPoint 
        let d = l - r
        return CGPoint(x: -d.y, y: d.x)
    

    static func + (l: CGPoint, r: CGPoint) -> CGPoint 
        CGPoint(
            x: l.x + r.x,
            y: l.y + r.y
        )
    

    static func - (l: CGPoint, r: CGPoint) -> CGPoint 
        CGPoint(
            x: l.x - r.x,
            y: l.y - r.y
        )
    

    static func / (point: CGPoint, divisor: CGFloat) -> CGPoint 
        CGPoint(
            x: point.x / divisor,
            y: point.y / divisor
        )
    


extension Sequence 

    func pairwise() -> Zip2Sequence<[Element?], Self> 
        zip([nil] + map(Optional.some), self)
    

显然,这只涉及CGPoints,而不是CLLocationCoordinate2D 或任何其他与地图相关的数据。不过,可以在视图中绘制:

下一步:定义一个自定义MKOverlayPathRenderer

final class BezierPolylineRenderer: MKOverlayPathRenderer 

    var tension: CGFloat = 2.5

    override func createPath() 
        guard let multiPoint = overlay as? MKMultiPoint else 
            assertionFailure("Expected MKMultiPoint")
            return
        

        let points = (0 ..< multiPoint.pointCount)
            .map  multiPoint.points()[$0] 
            .map  point(for: $0) 

        path = UIBezierPath.from(points, tension: tension).cgPath
    

    override func draw(
        _: MKMapRect,
        zoomScale: MKZoomScale,
        in context: CGContext
    ) 
        context.addPath(path)

        applyStrokeProperties(to: context, atZoomScale: zoomScale)

        context.setStrokeColor((strokeColor ?? .gray).cgColor)
        context.setLineWidth(lineWidth / zoomScale)

        context.strokePath()
    

最后:为您的地图添加一个委托以使用自定义渲染器

final class Delegate: NSObject, MKMapViewDelegate 

    func mapView(_: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer 
        if let polyline = overlay as? MKPolyline 
            let renderer = BezierPolylineRenderer(overlay: polyline)
            renderer.strokeColor = .blue
            renderer.lineWidth = 10
            return renderer
         else 
            fatalError("Unexpected overlay type: \(overlay)")
        
    


let delegate = Delegate()

let map = MKMapView()
map.delegate = delegate

map.addOverlay(MKPolyline(coordinates: coordinates, count: coordinates.count))

把它们放在一起,它看起来像这样:

如果您不喜欢兔子跳路径:

...还有其他方法可以计算贝塞尔曲线的控制点:

extension UIBezierPath 

    static func from(_ points: [CGPoint], tension: CGFloat) -> UIBezierPath 
        let path = UIBezierPath()
        guard let first = points.first, points.count > 1 else 
            return UIBezierPath()
        

        var derivatives: [CGPoint] = []
        for j in 0 ..< points.count 
            let prev = points[max(j - 1, 0)]
            let next = points[min(j + 1, points.count - 1)]
            derivatives.append((next - prev) / tension)
        

        path.move(to: first)
        for i in 1 ..< points.count 
            let cp1 = points[i - 1] + (derivatives[i - 1] / tension)
            let cp2 = points[i] - (derivatives[i] / tension)
            path.addCurve(to: points[i], controlPoint1: cp1, controlPoint2: cp2)
        

        return path
    

上述方法在当前点前后扫描,得到两个控制点,路径更平滑:

【讨论】:

以上是关于如何在 IOS 中弯曲折线(谷歌地图)?的主要内容,如果未能解决你的问题,请参考以下文章

如何在谷歌地图的折线中点绘制标记?

如何使用谷歌地图获取折线中的坐标?

如何保存谷歌地图绘制的折线路径?

如何使用多色折线绘制谷歌地图航路点

如何根据沿线的距离在谷歌地图折线上添加标记?

绘制折线捕捉到道路Android谷歌地图应用程序