How to draw an arc on Google Maps in iOS?

后端 未结 5 475
佛祖请我去吃肉
佛祖请我去吃肉 2020-12-11 07:26

How to draw an arc between two coordinate points in Google Maps like in this image and same like facebook post in iOS ?

相关标签:
5条回答
  • 2020-12-11 07:54

    Objective-C version @Rouny answer

    - (void)DrawCurvedPolylineOnMapFrom:(CLLocationCoordinate2D)startLocation To:(CLLocationCoordinate2D)endLocation
    {
        GMSMutablePath * path = [[GMSMutablePath alloc]init];
        [path addCoordinate:startLocation];
        [path addCoordinate:endLocation];
        // Curve Line
        double k = 0.2; //try between 0.5 to 0.2 for better results that suits you
        CLLocationDistance d = GMSGeometryDistance(startLocation, endLocation);
        float h = GMSGeometryHeading(startLocation , endLocation);
    
        //Midpoint position
        CLLocationCoordinate2D p = GMSGeometryOffset(startLocation, d * 0.5, h);
        //Apply some mathematics to calculate position of the circle center
        float x = (1-k*k)*d*0.5/(2*k);
        float r = (1+k*k)*d*0.5/(2*k);
        CLLocationCoordinate2D c = GMSGeometryOffset(p, x, h + -90.0);
    
        //Polyline options
        //Calculate heading between circle center and two points
        float h1 =  GMSGeometryHeading(c, startLocation);
        float h2 = GMSGeometryHeading(c, endLocation);
        //Calculate positions of points on circle border and add them to polyline options
        float numpoints = 100;
        float step = ((h2 - h1) / numpoints);
        for (int i = 0; i < numpoints; i++) {
            CLLocationCoordinate2D pi = GMSGeometryOffset(c, r, h1 + i * step);
            [path addCoordinate:pi];
        }
    
        //Draw polyline
        GMSPolyline * polyline = [GMSPolyline polylineWithPath:path];
        polyline.map = mapView;
        polyline.strokeWidth = 3.0;
        NSArray *styles = @[[GMSStrokeStyle solidColor:kBaseColor],
                            [GMSStrokeStyle solidColor:[UIColor clearColor]]];
    
        NSArray *lengths = @[@5, @5];
    
        polyline.spans = GMSStyleSpans(polyline.path, styles, lengths, kGMSLengthRhumb);
    
        GMSCoordinateBounds * bounds = [[GMSCoordinateBounds alloc]initWithCoordinate:startLocation coordinate:endLocation];
        UIEdgeInsets insets = UIEdgeInsetsMake(20, 20, 20, 20);
        GMSCameraPosition * camera = [mapView cameraForBounds:bounds insets:insets ];
        [mapView animateToCameraPosition:camera];
    
    }
    
    0 讨论(0)
  • 2020-12-11 07:57

    Before using the below function, don't forget to import GoogleMaps Credits: xomena

    func drawArcPolyline(startLocation: CLLocationCoordinate2D?, endLocation: CLLocationCoordinate2D?) {
        if let startLocation = startLocation, let endLocation = endLocation {
            //swap the startLocation & endLocation if you want to reverse the direction of polyline arc formed.
            let mapView = GMSMapView()
            let path = GMSMutablePath()
            path.add(startLocation)
            path.add(endLocation)
            // Curve Line
            let k: Double = 0.2 //try between 0.5 to 0.2 for better results that suits you
            let d = GMSGeometryDistance(startLocation, endLocation)
            let h = GMSGeometryHeading(startLocation, endLocation)
            //Midpoint position
            let p = GMSGeometryOffset(startLocation, d * 0.5, h)
            //Apply some mathematics to calculate position of the circle center
            let x = (1 - k * k) * d * 0.5 / (2 * k)
            let r = (1 + k * k) * d * 0.5 / (2 * k)
            let c = GMSGeometryOffset(p, x, h + 90.0)
            //Polyline options
            //Calculate heading between circle center and two points
            let h1 =  GMSGeometryHeading(c, startLocation)
            let h2 = GMSGeometryHeading(c, endLocation)
            //Calculate positions of points on circle border and add them to polyline options
            let numpoints = 100.0
            let step = ((h2 - h1) / Double(numpoints))
            for i in stride(from: 0.0, to: numpoints, by: 1) {
                let pi = GMSGeometryOffset(c, r, h1 + i * step)
                path.add(pi)
            }
            //Draw polyline
            let polyline = GMSPolyline(path: path)
            polyline.map = mapView // Assign GMSMapView as map
            polyline.strokeWidth = 3.0
            let styles = [GMSStrokeStyle.solidColor(UIColor.black), GMSStrokeStyle.solidColor(UIColor.clear)]
            let lengths = [20, 20] // Play with this for dotted line
            polyline.spans = GMSStyleSpans(polyline.path!, styles, lengths as [NSNumber], .rhumb)
            
            let bounds = GMSCoordinateBounds(coordinate: startLocation, coordinate: endLocation)
            let insets = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)
            let camera = mapView.camera(for: bounds, insets: insets)!
            mapView.animate(to: camera)
        }
    }
    
    0 讨论(0)
  • 2020-12-11 07:59

    None of the answers mentioned is a full proof solution. For a few locations, it draws a circle instead of a polyline. To resolve this we will calculate bearing(degrees clockwise from true north) and if it is less than zero, swap the start and end location.

    func createArc(
        startLocation: CLLocationCoordinate2D,
        endLocation: CLLocationCoordinate2D) -> GMSPolyline {
    
        var start = startLocation
        var end = endLocation
    
        if start.bearing(to: end) < 0.0 {
            start = endLocation
            end = startLocation
        }
    
        let angle = start.bearing(to: end) * Double.pi / 180.0
        let k = abs(0.3 * sin(angle))
    
        let path = GMSMutablePath()
        let d = GMSGeometryDistance(start, end)
        let h = GMSGeometryHeading(start, end)
        let p = GMSGeometryOffset(start, d * 0.5, h)
        let x = (1 - k * k) * d * 0.5 / (2 * k)
        let r = (1 + k * k) * d * 0.5 / (2 * k)
        let c = GMSGeometryOffset(p, x, h + 90.0)
        var h1 =  GMSGeometryHeading(c, start)
        var h2 = GMSGeometryHeading(c, end)
    
        if (h1 > 180) {
          h1 = h1 - 360
        }
    
        if (h2 > 180) {
          h2 = h2 - 360
        }
    
        let numpoints = 100.0
        let step = ((h2 - h1) / Double(numpoints))
        for i in stride(from: 0.0, to: numpoints, by: 1) {
          let pi = GMSGeometryOffset(c, r, h1 + i * step)
          path.add(pi)
        }
        path.add(end)
    
        let polyline = GMSPolyline(path: path)
        polyline.strokeWidth = 3.0
        polyline.spans = GMSStyleSpans(
          polyline.path!,
          [GMSStrokeStyle.solidColor(UIColor(hex: "#2962ff"))],
          [20, 20], .rhumb
        )
        return polyline
      }
    

    The bearing is the direction in which a vertical line on the map points, measured in degrees clockwise from north.

    func bearing(to point: CLLocationCoordinate2D) -> Double {
        func degreesToRadians(_ degrees: Double) -> Double { return degrees * Double.pi / 180.0 }
        func radiansToDegrees(_ radians: Double) -> Double { return radians * 180.0 / Double.pi }
    
        let lat1 = degreesToRadians(latitude)
        let lon1 = degreesToRadians(longitude)
    
        let lat2 = degreesToRadians(point.latitude);
        let lon2 = degreesToRadians(point.longitude);
    
        let dLon = lon2 - lon1;
    
        let y = sin(dLon) * cos(lat2);
        let x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dLon);
        let radiansBearing = atan2(y, x);
    
        return radiansToDegrees(radiansBearing)
      }
    
    0 讨论(0)
  • 2020-12-11 08:05

    The answer above does not handle all the corner cases, here is one that draws the arcs nicely:

    func drawArcPolyline(startLocation: CLLocationCoordinate2D?, endLocation: CLLocationCoordinate2D?) {
        if let _ = startLocation, let _ = endLocation {
            //swap the startLocation & endLocation if you want to reverse the direction of polyline arc formed.
    
            var start = startLocation!
            var end = endLocation!
            var gradientColors = GMSStrokeStyle.gradient(
                from: UIColor(red: 11.0/255, green: 211.0/255, blue: 200.0/255, alpha: 1),
                to: UIColor(red: 0/255, green: 44.0/255, blue: 66.0/255, alpha: 1))
    
            if startLocation!.heading(to: endLocation!) < 0.0 {
                // need to reverse the start and end, and reverse the color
                start = endLocation!
                end = startLocation!
    
                gradientColors = GMSStrokeStyle.gradient(
                    from: UIColor(red: 0/255, green: 44.0/255, blue: 66.0/255, alpha: 1),
                    to:  UIColor(red: 11.0/255, green: 211.0/255, blue: 200.0/255, alpha: 1))
            }
    
            let path = GMSMutablePath()
            // Curve Line
            let k = abs(0.3 * sin((start.heading(to: end)).degreesToRadians)) // was 0.3
    
    
            let d = GMSGeometryDistance(start, end)
            let h = GMSGeometryHeading(start, end)
            //Midpoint position
            let p = GMSGeometryOffset(start, d * 0.5, h)
            //Apply some mathematics to calculate position of the circle center
            let x = (1-k*k)*d*0.5/(2*k);
            let r = (1+k*k)*d*0.5/(2*k);
            let c = GMSGeometryOffset(p, x, h + 90.0)
    
            //Polyline options
            //Calculate heading between circle center and two points
            var h1 =  GMSGeometryHeading(c, start)
            var h2 = GMSGeometryHeading(c, end)
    
            if(h1>180){
                h1 = h1 - 360
            }
            if(h2>180){
                h2 = h2 - 360
            }
    
            //Calculate positions of points on circle border and add them to polyline options
            let numpoints = 100.0
            let step = (h2 - h1) / numpoints
            for i in stride(from: 0.0, to: numpoints, by: 1) {
                let pi = GMSGeometryOffset(c, r, h1 + i * step)
                path.add(pi)
            }
            path.add(end)
    
            //Draw polyline
            let polyline = GMSPolyline(path: path)
            polyline.map = mapView // Assign GMSMapView as map
            polyline.strokeWidth = 5.0
            polyline.spans = [GMSStyleSpan(style: gradientColors)]
        }
    }
    
    0 讨论(0)
  • 2020-12-11 08:08

    I used Bezier quadratic equation to draw curved lines. You can have a look on to the implementation. Here is the sample code.

    func bezierPath(from startLocation: CLLocationCoordinate2D, to endLocation: CLLocationCoordinate2D) -> GMSMutablePath {
    
            let distance = GMSGeometryDistance(startLocation, endLocation)
            let midPoint = GMSGeometryInterpolate(startLocation, endLocation, 0.5)
    
            let midToStartLocHeading = GMSGeometryHeading(midPoint, startLocation)
    
            let controlPointAngle = 360.0 - (90.0 - midToStartLocHeading)
            let controlPoint = GMSGeometryOffset(midPoint, distance / 2.0 , controlPointAngle)
            
            let path = GMSMutablePath()
            
            let stepper = 0.05
            let range = stride(from: 0.0, through: 1.0, by: stepper)// t = [0,1]
            
            func calculatePoint(when t: Double) -> CLLocationCoordinate2D {
                let t1 = (1.0 - t)
                let latitude = t1 * t1 * startLocation.latitude + 2 * t1 * t * controlPoint.latitude + t * t * endLocation.latitude
                let longitude = t1 * t1 * startLocation.longitude + 2 * t1 * t * controlPoint.longitude + t * t * endLocation.longitude
                let point = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
                return point
            }
            
            range.map { calculatePoint(when: $0) }.forEach { path.add($0) }
            return path
     }
    
    0 讨论(0)
提交回复
热议问题