How to get animated polyline route in GMSMapView, so that it move along with map when map is moved?
Asked Answered
C

6

20

I have created animated polyline like CAShapeLayer by following code, I have added CAShapeLayer as sublayer to GMSMapiew but, if I move the map the layer won't moves. where to add the layer, so that it move along with map?

   func layer(from path: GMSPath) -> CAShapeLayer {
        let breizerPath = UIBezierPath()
        let firstCoordinate: CLLocationCoordinate2D = path.coordinate(at: 0)
        breizerPath.move(to: self.mapView.projection.point(for: firstCoordinate))
        for i in 1 ..< Int((path.count())){
            print(path.coordinate(at: UInt(i)))
            let coordinate: CLLocationCoordinate2D = path.coordinate(at: UInt(i))
            breizerPath.addLine(to: self.mapView.projection.point(for: coordinate))
        }

        let shapeLayer = CAShapeLayer()
        shapeLayer.path = breizerPath.reversing().cgPath
        shapeLayer.strokeColor = UIColor.green.cgColor
        shapeLayer.lineWidth = 4.0
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.lineJoin = kCALineJoinRound
        shapeLayer.lineCap = kCALineCapRound
        shapeLayer.cornerRadius = 5
        return shapeLayer
    }

    func animatePath(_ layer: CAShapeLayer) {
        let pathAnimation = CABasicAnimation(keyPath: "strokeEnd")
        pathAnimation.duration = 6
        //pathAnimation.delegate = self
        pathAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
        pathAnimation.fromValue = Int(0.0)
        pathAnimation.toValue = Int(1.0)
        pathAnimation.repeatCount = 100
        layer.add(pathAnimation, forKey: "strokeEnd")
    }

Added to GoogleMapView by

    let shapelayer: CAShapeLayer = self.layer(from: path!)
    self.animatePath(shapelayer)
    self.mapView.layer.addSublayer(shapelayer)

I get the following output

Colter answered 6/3, 2017 at 8:9 Comment(1)
try to add this line as UIViewController's view subview, not GMSMapViewJonathonjonati
C
20

I am create GMSPath animation with path coordinate

enter image description here

Objective C

interface

@interface MapWithTracking () 

@property (weak, nonatomic) IBOutlet GMSMapView *mapView;

@property (nonatomic,strong) GMSMutablePath *path2;

@property (nonatomic,strong)NSMutableArray *arrayPolylineGreen;

@property (nonatomic,strong) GMSPolyline *polylineBlue;

@property (nonatomic,strong) GMSPolyline *polylineGreen;

@end

implementation

-(void)viewDidLoad {
    _arrayPolylineGreen = [[NSMutableArray alloc] init];
    _path2 = [[GMSMutablePath alloc]init];
}

Get a GMSPath and create a blue polyline.

-(void)createBluePolyline(GMSPath *path) {

       // Here create a blue poly line
      _polylineBlue = [GMSPolyline polylineWithPath:path];
      _polylineBlue.strokeColor = [UIColor blueColor];
      _polylineBlue.strokeWidth = 3;
      _polylineBlue.map = _mapView;

       // animate green path with timer
       [NSTimer scheduledTimerWithTimeInterval:0.003 repeats:true block:^(NSTimer * _Nonnull timer) {
             [self animate:path];
       }];

}

Animate a Green Polyline

Adding coordinate to path 2 and assign to map

-(void)animate:(GMSPath *)path {

    dispatch_async(dispatch_get_main_queue(), ^{
        if (i < path.count) {
            [_path2 addCoordinate:[path coordinateAtIndex:i]];
            _polylineGreen = [GMSPolyline polylineWithPath:_path2];
            _polylineGreen.strokeColor = [UIColor greenColor];
            _polylineGreen.strokeWidth = 3;
            _polylineGreen.map = _mapView;
            [arrayPolylineGreen addObject:_polylineGreen];
            i++;
        }
        else {
            i = 0;
            _path2 = [[GMSMutablePath alloc] init];

            for (GMSPolyline *line in arrayPolylineGreen) {
                line.map = nil;
            }

        }
    });
}
Classieclassification answered 16/3, 2017 at 5:24 Comment(11)
I've checked your code, it's pretty easy to read. But I've one question, Is Polyline will always draw after 3 milliseconds? I mean if latitude and longitude are complex structure then it will take time to draw on path. (depend on main thread load)Burn
You can set second as per your requirement. If there is a more complex structure of path then it work like charm because in main thread we are not doing a complicated operation. you can also doing it background thread but animation is not working like charm.Classieclassification
yes problem is that if I use this animation with 3 millisecond then it's not always same animation in all devices. I've used DispatchQueue for this operation. Is it fine or I need to use nsTimer? Can you please advice me?Burn
In above case i will use timer because i added a new path coordinate in every 0.003 seconds so it is working in all devices. from my point of view time is easy way for use that kind operation.Classieclassification
i will just add new point and Polyline into map and remove after competition. you can find the way for using only one polyline so its using low memory and improve performance also. in above case i using a array of polyline.Classieclassification
Yes I've used same code and I also required same operation you did. But problem is that when there is rounded path like circle and road path is not easy to drive(like strait road) then it's not animation proper and also polyline takes more time then 3 milliseconds even I set it to 3 millisecond time. Can you please suggest me?Burn
also I've checked when we draw path more than 30 kms and road are not strait forward then it's taking more time.Burn
Yeah this take more time because when road is round than path have a more coordinate then the strait road. you can try to skip one coordinate to draw path it is only one possible way.Classieclassification
you can also see in above example if path is strait then line go fast then the round path and distance also increases then the round pathClassieclassification
how to stop timer sir?Stets
@Stets set timer as NSTimer * myTimer = [NSTimer scheduledTimerWithTimeInterval:110.0 target:self selector:@selector(targetMethod:) userInfo:nil repeats:YES]; Stop timer [myTimer invalidate];Classieclassification
C
29

SWIFT

Declartion

var polyline = GMSPolyline()
var animationPolyline = GMSPolyline()
var path = GMSPath()
var animationPath = GMSMutablePath()
var i: UInt = 0
var timer: Timer!

To Darw Route

func drawRoute(routeDict: Dictionary<String, Any>) {

        let routesArray = routeDict ["routes"] as! NSArray

        if (routesArray.count > 0)
        {
            let routeDict = routesArray[0] as! Dictionary<String, Any>
            let routeOverviewPolyline = routeDict["overview_polyline"] as! Dictionary<String, Any>
            let points = routeOverviewPolyline["points"]
            self.path = GMSPath.init(fromEncodedPath: points as! String)!

            self.polyline.path = path
            self.polyline.strokeColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.5)
            self.polyline.strokeWidth = 3.0
            self.polyline.map = self.mapView

            self.timer = Timer.scheduledTimer(timeInterval: 0.003, target: self, selector: #selector(animatePolylinePath), userInfo: nil, repeats: true)
        }
    }

To Animate Path

func animatePolylinePath() {
        if (self.i < self.path.count()) {
            self.animationPath.add(self.path.coordinate(at: self.i))
            self.animationPolyline.path = self.animationPath
            self.animationPolyline.strokeColor = UIColor.black
            self.animationPolyline.strokeWidth = 3
            self.animationPolyline.map = self.mapView
            self.i += 1
        }
        else {
            self.i = 0
            self.animationPath = GMSMutablePath()
            self.animationPolyline.map = nil
        }
    }

Don't forgot to stop timer in viewWillDisappear

self.timer.invalidate()

Output

enter image description here

Colter answered 16/3, 2017 at 11:43 Comment(8)
Tx to convert in swiftClassieclassification
Thanx a lot for swift conversion!Orellana
HI in os version 10.3.1(iphone 5) its not working perfect. some times become slow and fast running in device. In iphone 6 it become slow in os 10.3.1 while in os 10.0 its working.Hokku
It causing memory problem if app is in background timer still running and causing app terminate with memory consumptionBernstein
Have you tried with this with shot route. The animation is not smooth for short route like 2-3 kms.Warhol
I agree with @Mahendra. In short routes, the animation is very quick. Can we slow it down anyhow?Vireo
can we do it with CA transaction?Helenehelenka
@SagunRajLage You have to maintain the animation time with respect to the distance between two points.Pigweed
C
20

I am create GMSPath animation with path coordinate

enter image description here

Objective C

interface

@interface MapWithTracking () 

@property (weak, nonatomic) IBOutlet GMSMapView *mapView;

@property (nonatomic,strong) GMSMutablePath *path2;

@property (nonatomic,strong)NSMutableArray *arrayPolylineGreen;

@property (nonatomic,strong) GMSPolyline *polylineBlue;

@property (nonatomic,strong) GMSPolyline *polylineGreen;

@end

implementation

-(void)viewDidLoad {
    _arrayPolylineGreen = [[NSMutableArray alloc] init];
    _path2 = [[GMSMutablePath alloc]init];
}

Get a GMSPath and create a blue polyline.

-(void)createBluePolyline(GMSPath *path) {

       // Here create a blue poly line
      _polylineBlue = [GMSPolyline polylineWithPath:path];
      _polylineBlue.strokeColor = [UIColor blueColor];
      _polylineBlue.strokeWidth = 3;
      _polylineBlue.map = _mapView;

       // animate green path with timer
       [NSTimer scheduledTimerWithTimeInterval:0.003 repeats:true block:^(NSTimer * _Nonnull timer) {
             [self animate:path];
       }];

}

Animate a Green Polyline

Adding coordinate to path 2 and assign to map

-(void)animate:(GMSPath *)path {

    dispatch_async(dispatch_get_main_queue(), ^{
        if (i < path.count) {
            [_path2 addCoordinate:[path coordinateAtIndex:i]];
            _polylineGreen = [GMSPolyline polylineWithPath:_path2];
            _polylineGreen.strokeColor = [UIColor greenColor];
            _polylineGreen.strokeWidth = 3;
            _polylineGreen.map = _mapView;
            [arrayPolylineGreen addObject:_polylineGreen];
            i++;
        }
        else {
            i = 0;
            _path2 = [[GMSMutablePath alloc] init];

            for (GMSPolyline *line in arrayPolylineGreen) {
                line.map = nil;
            }

        }
    });
}
Classieclassification answered 16/3, 2017 at 5:24 Comment(11)
I've checked your code, it's pretty easy to read. But I've one question, Is Polyline will always draw after 3 milliseconds? I mean if latitude and longitude are complex structure then it will take time to draw on path. (depend on main thread load)Burn
You can set second as per your requirement. If there is a more complex structure of path then it work like charm because in main thread we are not doing a complicated operation. you can also doing it background thread but animation is not working like charm.Classieclassification
yes problem is that if I use this animation with 3 millisecond then it's not always same animation in all devices. I've used DispatchQueue for this operation. Is it fine or I need to use nsTimer? Can you please advice me?Burn
In above case i will use timer because i added a new path coordinate in every 0.003 seconds so it is working in all devices. from my point of view time is easy way for use that kind operation.Classieclassification
i will just add new point and Polyline into map and remove after competition. you can find the way for using only one polyline so its using low memory and improve performance also. in above case i using a array of polyline.Classieclassification
Yes I've used same code and I also required same operation you did. But problem is that when there is rounded path like circle and road path is not easy to drive(like strait road) then it's not animation proper and also polyline takes more time then 3 milliseconds even I set it to 3 millisecond time. Can you please suggest me?Burn
also I've checked when we draw path more than 30 kms and road are not strait forward then it's taking more time.Burn
Yeah this take more time because when road is round than path have a more coordinate then the strait road. you can try to skip one coordinate to draw path it is only one possible way.Classieclassification
you can also see in above example if path is strait then line go fast then the round path and distance also increases then the round pathClassieclassification
how to stop timer sir?Stets
@Stets set timer as NSTimer * myTimer = [NSTimer scheduledTimerWithTimeInterval:110.0 target:self selector:@selector(targetMethod:) userInfo:nil repeats:YES]; Stop timer [myTimer invalidate];Classieclassification
D
3

This is an adaptation of the code from Elangovan

The changes that I did was to remove the var from the class to be just in the function and also removed the #selector that is no longer need in iOS >= 10.

var timerAnimation: Timer!
var mapView:GMSMapView?



    func drawRoute(encodedString: String, animated: Bool) {

        if let path = GMSMutablePath(fromEncodedPath: encodedString) {

            let polyline = GMSPolyline(path: path)
            polyline.strokeWidth = 3.0
            polyline.strokeColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.5)
            polyline.map = Singleton.shared.getMapView()

            if(animated){
                self.animatePolylinePath(path: path)
            }
        }
    }

    func animatePolylinePath(path: GMSMutablePath) {

        var pos: UInt = 0
        var animationPath = GMSMutablePath()
        let animationPolyline = GMSPolyline()
        self.timerAnimation = Timer.scheduledTimer(withTimeInterval: 0.003, repeats: true) { timer in

            pos += 1
            if(pos >= path.count()){
                pos = 0
                animationPath = GMSMutablePath()
                animationPolyline.map = nil
            }
            animationPath.add(path.coordinate(at: pos))
            animationPolyline.path = animationPath
            animationPolyline.strokeColor = UIColor.yellow
            animationPolyline.strokeWidth = 3
            animationPolyline.map = self.mapView
        }
    }


    func stopAnimatePolylinePath() {

        self.timerAnimation.invalidate()
    }
Diamagnet answered 6/5, 2018 at 22:51 Comment(0)
V
2

Move the layer whenever the map moves by implementing the delegate mapView:didChangeCameraPosition:.

Viosterol answered 14/3, 2017 at 14:41 Comment(4)
I have created breizerpath path from GMSPath coordinates and created CAShapeLayer from breizerpath. how to change layer frame to cameraPosition? @JonColter
You can convert from coordinate to point and a point to coordinate using the projection property on the GMSMapView. see developers.google.com/maps/documentation/ios-sdk/reference/…Viosterol
@JonRose can u provide code which you have used to animate or move layer?Helenehelenka
@Colter did u get any solution?Helenehelenka
C
0

You need to use GMSPolyline for this. Create a GMSPolyline instance, set your GMSMapView instance to be it's parent map.

GMSPolyline* routeOverlay = // config
routeOverlay.map = // my GMSMapView instance

That's all. You don't need to do anything extra to make it move with the map camera movement. It does that automatically.

Cruciform answered 15/3, 2017 at 7:4 Comment(6)
The question asks for a way to animate that path which I don't think you can do with a GMSPolylineViosterol
@JonRose You can create a GMSPolyline instance with a GMSPath instance. You can create a GMSPath with the encodedPath string received from Google Places APIs for finding the route between two locations. I assume Elangovan is already using this setup.Cruciform
Can you animate that path?Viosterol
@JonRose Animating GMSPolyline isn't possible. A workaround is to use CAShapeLayer for drawing animation and as soon as the animation completes, use an actual GMSPolyline. Reference - #38222141Cruciform
great idea. Now let say you want the animation to be constantly repeating so it is always on the screen. How do you get the shapeLayer to move when the map moves?Viosterol
I have not encountered such use case. The question didn't mention it either. However in that case, we need to make shapeLayer follow map's movement.Cruciform
F
0

You can create a variable for shapeLayer and use the GMSMapViewDelegate methods mapView(_ mapView: GMSMapView, willMove gesture: Bool) and mapView(_ mapView: GMSMapView, idleAt position: GMSCameraPosition) to add and remove the layer from the map. There are two downsides to this approach, first is that the layer doesn't animate when the map is being dragged(moved) and second downside is that the layer always sits on top of all other map elements like markers, road names, POIs etc. I couldn't find a way to add this layer as a sublayer directly to the ground overlay. You can find the full code below:

var polyLineShapeLayer:CAShapeLayer?

 var layerAdded = false

 var path = GMSPath()

 var polyLine:GMSPolyline!



// Add regular polyline to the map 

func addPolyLineWithEncodedStringInMap(_ encodedString:String) {
        self.polyLine = GMSPolyline(path: self.path)
        polyLine.strokeWidth = 3.8
        self.polyLine.strokeColor = .black
        polyLine.map = googleMapView

        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2) {
            self.addPolyLineShapeLayerToMapView()
            self.layerAdded = true
        }

    }


// Add CAshapeLayer to map 

  func layer(from path: GMSPath) -> CAShapeLayer {
        let breizerPath = UIBezierPath()
        let firstCoordinate: CLLocationCoordinate2D = path.coordinate(at: 0)
        breizerPath.move(to: self.googleMapView.projection.point(for: firstCoordinate))
        for i in 1 ..< Int((path.count())){
            print(path.coordinate(at: UInt(i)))
            let coordinate: CLLocationCoordinate2D = path.coordinate(at: UInt(i))
            breizerPath.addLine(to: self.googleMapView.projection.point(for: coordinate))
        }

        let shapeLayer = CAShapeLayer()
        shapeLayer.path = breizerPath.cgPath
        shapeLayer.strokeColor = UIColor.lightGray.withAlphaComponent(0.8).cgColor
        shapeLayer.lineWidth = 4.0
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.lineJoin = kCALineJoinRound
        shapeLayer.lineCap = kCALineCapRound
        shapeLayer.cornerRadius = 5
        return shapeLayer
    }

    func animatePath(_ layer: CAShapeLayer) {
        let pathAnimation = CABasicAnimation(keyPath: "strokeEnd")
        pathAnimation.duration = 2
        pathAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
        pathAnimation.fromValue = Int(0.0)
        pathAnimation.toValue = Int(1.0)
        pathAnimation.repeatCount = 200
        layer.add(pathAnimation, forKey: "strokeEnd")
    }

    func addPolyLineShapeLayerToMapView(){

        polyLineShapeLayer = self.layer(from: self.path)

        if let polyLineShapeLayer = polyLineShapeLayer{
            self.animatePath(polyLineShapeLayer)
            self.googleMapView.layer.addSublayer(polyLineShapeLayer)
            polyLineShapeLayer.zPosition = 0

        }

    }


// Delegate methods to control the polyline 


// whenever map is about to move, if layer is already added, remove the layer from superLayer 

    func mapView(_ mapView: GMSMapView, willMove gesture: Bool) {
        if layerAdded{
            DispatchQueue.main.async {
                self.polyLineShapeLayer?.removeFromSuperlayer()
                self.polyLineShapeLayer = nil
            }
        }
    }

// when map is idle again(var layerAdded:bool ensures that additional layer is not added initially when the delegate method is fired) add new instance of polylineShapeLayer to the map with current projected coordinates. 

    func mapView(_ mapView: GMSMapView, idleAt position: GMSCameraPosition) {
        if self.layerAdded{
            self.addPolyLineShapeLayerToMapView()
        }

    }
Frescobaldi answered 18/8, 2018 at 11:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.