Rendering multiple polylines on MapView

6.1k views Asked by At

I have two sets of coordinates and trying to draw polylines on map view. I'm able to draw a polyline with one set of coordinates but I couldn't able to draw two polylines.

Below is the code...

func drawRouteOnMap()
{
    var centerCoordinate : CLLocationCoordinate2D = CLLocationCoordinate2DMake(icRouteLat[0], icRouteLong[0])
    let span = MKCoordinateSpanMake(0.001, 0.001)
    var centerPosition = MKCoordinateRegionMake(centerCoordinate, span)
    mapView.setRegion(centerPosition,animated:true)
    self.mapView.mapType = MKMapType.Hybrid

    routePointer = "one";
    // first route line
    //----------able to draw polyline with this set of coordinates
    for i in 0..<routeLat.count-1
    {
        var fromCoordinate :CLLocation = CLLocation(latitude: routeLat[i], longitude: routeLong[i])
        var toCoordinate   :CLLocation = CLLocation(latitude: routeLat[i+1], longitude: routeLong[i+1])
        var locations = [fromCoordinate, toCoordinate];
        var coordinates = locations.map({(location: CLLocation) -> CLLocationCoordinate2D in return location.coordinate});
        var polyLine = MKPolyline(coordinates: &coordinates, count: locations.count);
        mapView.addOverlay(polyLine);
    }

    routePointer = "second";
    // IC route line
    //------Polyline for this set of coordinates doesn't appear on map view
    for j in 0..<icRouteLat.count-1
    {
        var fromCoordinateIC :CLLocation = CLLocation(latitude: icRouteLat[j], longitude: icRouteLat[j])
        var toCoordinateIC :CLLocation = CLLocation(latitude: icRouteLong[j+1], longitude: icRouteLong[j+1])
        var locationsIC = [fromCoordinateIC, toCoordinateIC];
        var coordinatesIC = locationsIC.map({(location: CLLocation) -> CLLocationCoordinate2D in return location.coordinate});
        var polyLineIC = MKPolyline(coordinates: &coordinatesIC, count: locationsIC.count);
        mapView.addOverlay(polyLineIC);
    }
}

func mapView(mapView: MKMapView!, rendererForOverlay overlay: MKOverlay!) -> MKOverlayRenderer! {
    let route: MKPolyline = overlay as MKPolyline
    let routeRenderer = MKPolylineRenderer(polyline:route)
    routeRenderer.lineWidth = 3.0
    if routePointer == "one"
    {
        routeRenderer.strokeColor = UIColor(red: 240.0/255.0, green: 68.0/255.0, blue: 0.0/255.0, alpha: 1);
    }
    else if routePointer == "second"
    {
        routeRenderer.strokeColor = UIColor(red: 45.0/255.0, green: 200.0/255.0, blue: 0.0/255.0, alpha: 1);
    }
    return routeRenderer
}

The arrays routeLat[], routeLong[] and icRouteLat[] and icRouteLong[] are two set of coordinates.

Is there anything I'm missing out here? Or is this the right way to implement?

EDIT

Updated code based on the suggestions...

func drawRouteOnMap()
{
    var centerCoordinate : CLLocationCoordinate2D = CLLocationCoordinate2DMake(icRouteLat[0], icRouteLong[0])
    let span = MKCoordinateSpanMake(0.001, 0.001)
    var centerPosition = MKCoordinateRegionMake(centerCoordinate, span)
    mapView.setRegion(centerPosition,animated:true)
    self.mapView.mapType = MKMapType.Hybrid
    var polyLine: MKPolyline!
    var polyLineIC: MKPolyline!

    // first route line
    for i in 0..<routeLat.count-1
    {
        var fromCoordinate :CLLocation = CLLocation(latitude: routeLat[i], longitude: routeLong[i])
        var toCoordinate   :CLLocation = CLLocation(latitude: routeLat[i+1], longitude: routeLong[i+1])
        var locations = [fromCoordinate, toCoordinate];
        var coordinates = locations.map({(location: CLLocation) -> CLLocationCoordinate2D in return location.coordinate});
        polyLine = MKPolyline(coordinates: &coordinates, count: locations.count);
        polyLine.title = "one";
        mapView.addOverlay(polyLine);
    }

    // IC route line
    for j in 0..<icRouteLat.count-1
    {
        var fromCoordinateIC :CLLocation = CLLocation(latitude: icRouteLat[j], longitude: icRouteLong[j])
        var toCoordinateIC :CLLocation = CLLocation(latitude: icRouteLat[j+1], longitude: icRouteLong[j+1])
        var locationsIC = [fromCoordinateIC, toCoordinateIC];
        var coordinatesIC = locationsIC.map({(location: CLLocation) -> CLLocationCoordinate2D in return location.coordinate});
        polyLineIC = MKPolyline(coordinates: &coordinatesIC, count: locationsIC.count);
        polyLineIC.title = "ic";
        mapView.addOverlay(polyLineIC);
    }
}

func mapView(mapView: MKMapView!, rendererForOverlay overlay: MKOverlay!) -> MKOverlayRenderer! {
    if overlay is MKPolyline
    {
        let route: MKPolyline = overlay as MKPolyline
        let routeRenderer = MKPolylineRenderer(polyline:route)
        routeRenderer.lineWidth = 3.0
        if overlay.title == "one"
        {
            routeRenderer.strokeColor = UIColor(red: 240.0/255.0, green: 68.0/255.0, blue: 0.0/255.0, alpha: 1);
        }
        else if overlay.title == "ic"
        {
            routeRenderer.strokeColor = UIColor(red: 45.0/255.0, green: 200.0/255.0, blue: 0.0/255.0, alpha: 1);
        }
        return routeRenderer
    }

    return nil
}

FINAL EDIT

Updated code:

func drawRouteOnMap()
{
    var centerCoordinate : CLLocationCoordinate2D = icRoute[0];
    let span = MKCoordinateSpanMake(0.001, 0.001)
    var centerPosition = MKCoordinateRegionMake(centerCoordinate, span)
    mapView.setRegion(centerPosition,animated:true)
    self.mapView.mapType = MKMapType.Hybrid

    // first route line
    polyLine = MKPolyline(coordinates: &firstRoute, count: firstRoute.count);
    polyLine.title = "one";
    mapView.addOverlay(polyLine);

    // IC route line
    polyLine = MKPolyline(coordinates: &icRoute, count: icRoute.count);
    polyLine.title = "ic";
    mapView.addOverlay(polyLine);

}

func mapView(mapView: MKMapView!, rendererForOverlay overlay: MKOverlay!) -> MKOverlayRenderer! {
    if overlay is MKPolyline
    {
        let route: MKPolyline = overlay as MKPolyline
        let routeRenderer = MKPolylineRenderer(polyline:route)
        routeRenderer.lineWidth = 3.0
        if overlay.title == "one"
        {
            routeRenderer.strokeColor = UIColor(red: 240.0/255.0, green: 68.0/255.0, blue: 0.0/255.0, alpha: 1);
        }
        else if overlay.title == "ic"
        {
            routeRenderer.strokeColor = UIColor(red: 45.0/255.0, green: 200.0/255.0, blue: 0.0/255.0, alpha: 1);
        }
        return routeRenderer
    }

    return nil
}
1

There are 1 answers

6
AudioBubble On BEST ANSWER

The main problem is in the second for loop:

var fromCoordinateIC :CLLocation = 
    CLLocation(latitude: icRouteLat[j], 
              longitude: icRouteLat[j])  //<-- should be icRouteLong

var toCoordinateIC :CLLocation = 
    CLLocation(latitude: icRouteLong[j+1], //<-- should be icRouteLat
              longitude: icRouteLong[j+1])

The second line is not visible (or not where you expect) because it is getting the wrong coordinates.


There are, however, some additional things (not causing or related to the main issue) I'd like to point out:

  1. In the rendererForOverlay delegate method, the code is using the outside variable routePointer to set the color of the line. This is not recommended. You must not assume when or how often a delegate method will be called. There is no guarantee the delegate method will be called immediately after doing addOverlay. It's also possible for the delegate method to be called multiple times for the same overlay.

    For this reason, you must use data directly associated with the incoming overlay object to set the renderer's properties. In the current example, a better alternative is to set each polyline's title property to "one" or "second" and eliminate the routePointer variable.

  2. In the rendererForOverlay delegate method, it would be better to first check if overlay is of type MKPolyline before treating it like one. You may later want to add other types of overlays such as MKCircle or MKPolygon.

  3. Currently, the code is creating a separate MKPolyline for each line segment in each route. So if route "one" has 10 line segments and route "second" has 15, the code is currently adding 25 overlays. This is not necessary. An MKPolyline can draw multiple line segments. It would be much more efficient to add all the coordinates for a route into an array and after the loop, create and add the MKPolyline. That way, you would only be adding 2 overlays.

    Example of creating a single overlay for each route instead of creating multiple overlays for each route (one for each line segment in the route):

    //First add all the coordinates to an array...
    var coordinates: [CLLocationCoordinate2D] = [CLLocationCoordinate2D]()
    for i in 0 ..< routeLat.count
    {
        var coordinate  = CLLocationCoordinate2DMake(routeLat[i], routeLong[i])
        coordinates.append(coordinate)
    }
    //Then create a single overlay with ALL the coordinates in the route...
    polyLine = MKPolyline(coordinates: &coordinates, count: coordinates.count);
    polyLine.title = "one";
    mapView.addOverlay(polyLine);
    
  4. It's not necessary to define separate arrays for latitude and longitude where each array is a list of doubles. It would be much more efficient and will simplify the code significantly to keep a single array of coordinates (of type CLLocationCoordinate2D) for each route. An MKPolyline can then be created from this array without any looping.

    Example of using single array of CLLocationCoordinate2Ds for each route:

    var routeOneCoordinates = [CLLocationCoordinate2DMake(30.0, -87.0),
                                CLLocationCoordinate2DMake(32.0, -84.0),
                                CLLocationCoordinate2DMake(31.5, -83.5),
                                CLLocationCoordinate2DMake(31.0, -83.0),
                                CLLocationCoordinate2DMake(33.5, -82.0)]
    polyLine = MKPolyline(coordinates: &routeOneCoordinates, count: routeOneCoordinates.count);
    polyLine.title = "one";
    mapView.addOverlay(polyLine);