Update polyline as user moves - ios

I am using GoogleMaps to draw route. What I want to do is when user travels on that route remove the line which is already travelled(Like Uber does). I guess we can do it with removing the points from the polyline and redraw it. Is it the correct approach?
How can I know that those points are travelled and need to update the path?

1) Create Globle Variable
var demoPolyline = GMSPolyline()
var demoPolylineOLD = GMSPolyline()
// Set Destination Location Cordinates
var destinationLocation = CLLocation(latitude: 23.072837, longitude: 72.516455)
2) Use CLLocationManagerDelegate Method For update current location
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location: CLLocation = locations.last!
let originalLoc: String = "\(location.coordinate.latitude),\(location.coordinate.longitude)"
let destiantionLoc: String = "\(destinationLocation.coordinate.latitude),\(destinationLocation.coordinate.longitude)"
let latitudeDiff: Double = Double(location.coordinate.latitude) - Double(destinationLocation.coordinate.latitude)
let longitudeDiff: Double = Double(location.coordinate.longitude) - Double(destinationLocation.coordinate.longitude)
let waypointLatitude = location.coordinate.latitude - latitudeDiff
let waypointLongitude = location.coordinate.longitude - longitudeDiff
getDirectionsChangedPolyLine(origin: originalLoc, destination: destiantionLoc, waypoints: ["\(waypointLatitude),\(waypointLongitude)"], travelMode: nil, completionHandler: nil)
}
3) Create Method For Draw and update Polyline on Google map
func getDirectionsChangedPolyLine(origin: String!, destination: String!, waypoints: Array<String>!, travelMode: AnyObject!, completionHandler: ((_ status: String, _ success: Bool) -> Void)?)
{
DispatchQueue.main.asyncAfter(deadline: .now()) {
if let originLocation = origin {
if let destinationLocation = destination {
var directionsURLString = "https://maps.googleapis.com/maps/api/directions/json?" + "origin=" + originLocation + "&destination=" + destinationLocation
if let routeWaypoints = waypoints {
directionsURLString += "&waypoints=optimize:true"
for waypoint in routeWaypoints {
directionsURLString += "|" + waypoint
}
}
directionsURLString = directionsURLString.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)!
let directionsURL = NSURL(string: directionsURLString)
DispatchQueue.main.async( execute: { () -> Void in
let directionsData = NSData(contentsOf: directionsURL! as URL)
do{
let dictionary: Dictionary<String, AnyObject> = try JSONSerialization.jsonObject(with: directionsData! as Data, options: JSONSerialization.ReadingOptions.mutableContainers) as! Dictionary<String, AnyObject>
let status = dictionary["status"] as! String
if status == "OK" {
self.selectedRoute = (dictionary["routes"] as! Array<Dictionary<String, AnyObject>>)[0]
self.overviewPolyline = self.selectedRoute["overview_polyline"] as! Dictionary<String, AnyObject>
let route = self.overviewPolyline["points"] as! String
let path: GMSPath = GMSPath(fromEncodedPath: route)!
self.demoPolylineOLD = self.demoPolyline
self.demoPolylineOLD.strokeColor = UIColor.blue
self.demoPolylineOLD.strokeWidth = 3.0
self.demoPolylineOLD.map = self.mapView
self.demoPolyline.map = nil
self.demoPolyline = GMSPolyline(path: path)
self.demoPolyline.map = self.mapView
self.demoPolyline.strokeColor = UIColor.blue
self.demoPolyline.strokeWidth = 3.0
self.demoPolylineOLD.map = nil
} else {
self.getDirectionsChangedPolyLine(origin: origin, destination: destination, waypoints: waypoints, travelMode: travelMode, completionHandler: completionHandler)
}
} catch {
self.getDirectionsChangedPolyLine(origin: origin, destination: destination, waypoints: waypoints, travelMode: travelMode, completionHandler: completionHandler)
}
})
} else {
print("Destination Location Not Found")
}
} else {
print("Origin Location Not Found")
}
}
}
This is working on my live project
I hope this will work for everybody

Related

Iterate through JSON array and add coordinates to the map

I'm using an API to get latitude and longitude coordinates and place them on a map with the name of the place it corresponds to. I'm able to put one place's lat and long coordinates but I'm not too sure how to add all of them to a map. I can't get my head around how to do it. I've tried to use a for loop to do it but I'm too sure on how I would implement it. This is what I've got so far:
func getData() {
let url = "https://www.givefood.org.uk/api/2/foodbanks/"
let task = URLSession.shared.dataTask(with: URL(string: url)!, completionHandler: { [self] data, response, error in
guard let data = data, error == nil else {
print("Wrong")
return
}
var result: [Info]?
do {
result = try JSONDecoder().decode([Info].self, from: data)
}
catch {
print("Failed to convert: \(error.localizedDescription)")
}
guard let json = result else {
return
}
for each in json {
var each = 0
each += 1
let comp = json[each].lat_lng?.components(separatedBy: ",")
let latString = comp![each]
let lonString = comp![each]
let lat = Double(latString)
let lon = Double(lonString)
let locationPin: CLLocationCoordinate2D = CLLocationCoordinate2DMake(lat!, lon!)
let location: CLLocationCoordinate2D = CLLocationCoordinate2DMake(51.55573, -0.108312)
let region = MKCoordinateRegion.init(center: location, latitudinalMeters: regionInMetres, longitudinalMeters: regionInMetres)
mapView.setRegion(region, animated: true)
let myAn1 = MapPin(title: json[each].name!, locationName: json[each].name!, coordinate: locationPin)
mapView.addAnnotations([myAn1])
}
})
task.resume()
}
Your loop is wrong, each after for is one Info item. The Int index each is pointless and you set it in each iteration to zero so you get always the same coordinate (at index 1).
First of all declare name and lat_lng as non-optional. All records contain both fields.
struct Info : Decodable {
let lat_lng : String
let name : String
}
Second of all for convenience reasons extend CLLocationCoordinate2D to create a coordinate from a string
extension CLLocationCoordinate2D {
init?(string: String) {
let comp = string.components(separatedBy: ",")
guard comp.count == 2, let lat = Double(comp[0]), let lon = Double(comp[1]) else { return nil }
self.init(latitude: lat, longitude: lon )
}
}
Third of all put all good code into the do scope instead of dealing with optionals and set the region once before the loop
func getData() {
let url = "https://www.givefood.org.uk/api/2/foodbanks/"
let task = URLSession.shared.dataTask(with: URL(string: url)!, completionHandler: { [self] data, response, error in
if let error = error { print(error); return }
do {
let result = try JSONDecoder().decode([Info].self, from: data!)
let location = CLLocationCoordinate2D(latitude: 51.55573, longitude: -0.108312)
let region = MKCoordinateRegion.init(center: location, latitudinalMeters: regionInMetres, longitudinalMeters: regionInMetres)
mapView.setRegion(region, animated: true)
var pins = [MapPin]()
for info in result {
if let coordinate = CLLocationCoordinate2D(string: info.lat_lng) {
pins.append(MapPin(title: info.name, locationName: info.name, coordinate: coordinate))
}
}
DispatchQueue.main.async {
self.mapView.addAnnotations(pins)
}
}
catch {
print("Failed to convert: \(error)")
}
})
task.resume()
}

PolyLines not showing in google maps

I want show path between destination and source on google map. I am google direction api's getting route between of co-ordinates , I am getting response and set on google map but not showing on map . My code is
func getPolylineRoute(from source: CLLocationCoordinate2D, to destination: CLLocationCoordinate2D){
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
let url = URL(string: "https://maps.googleapis.com/maps/api/directions/json?origin=\(source.latitude),\(source.longitude)&destination=\(destination.latitude),\(destination.longitude)&sensor=true&mode=driving&key=AIzaSyAyU5txJ86b25-_l0DW-IldSKGGYqQJn3M")!
let task = session.dataTask(with: url, completionHandler: {
(data, response, error) in
DispatchQueue.main.async {
if error != nil {
print(error!.localizedDescription)
AppManager.dissmissHud()
}
else {
do {
if let json : [String:Any] = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String: Any]{
guard let routes = json["routes"] as? NSArray else {
DispatchQueue.main.async {
AppManager.dissmissHud()
}
return
}
if (routes.count > 0) {
let overview_polyline = routes[0] as? NSDictionary
let dictPolyline = overview_polyline?["overview_polyline"] as? NSDictionary
let points = dictPolyline?.object(forKey: "points") as? String
self.showPath(polyStr: points!)
DispatchQueue.main.async {
AppManager.dissmissHud()
let bounds = GMSCoordinateBounds(coordinate: source, coordinate: destination)
let update = GMSCameraUpdate.fit(bounds, with: UIEdgeInsetsMake(75, 20, 20, 20))
self.vwMap!.moveCamera(update)
}
}
else {
DispatchQueue.main.async {
AppManager.dissmissHud()
}
}
}
}
catch {
print("error in JSONSerialization")
DispatchQueue.main.async {
AppManager.dissmissHud()
}
}
}
}
})
task.resume()
}
func drawPlyLineOnMap() {
let source : CLLocationCoordinate2D = CLLocationCoordinate2DMake(Double((model?.fromAddressLatitude)!), Double((model?.fromAddressLongtitude)!))
let destination : CLLocationCoordinate2D = CLLocationCoordinate2DMake(Double((model?.toAddressLatitude)!), Double((model?.toAddressLongtitude)!))
self.vwMap.clear()
//Source pin
let marker = GMSMarker()
let markerImage = UIImage(named: "from_pin")!.withRenderingMode(.alwaysOriginal)
let markerView = UIImageView(image: markerImage)
marker.position = source
marker.iconView = markerView
//marker.userData = dict
marker.map = vwMap
//Destination pin
let markerTo = GMSMarker()
let markerImageTo = UIImage(named: "to_red_pin")!.withRenderingMode(.alwaysOriginal)
let markerViewTo = UIImageView(image: markerImageTo)
markerTo.position = destination
// marker.userData = dict
markerTo.iconView = markerViewTo
markerTo.map = vwMap
var arrAdTemp:[AddressTableModel] = []
arrAdTemp.append(contentsOf: arrAddresses)
arrAdTemp.removeLast()
arrAdTemp.removeFirst()
for obj in arrAdTemp {
print(obj.strLatitude)
print(obj.strLongtitude)
let stopOver : CLLocationCoordinate2D = CLLocationCoordinate2DMake(obj.strLatitude, obj.strLongtitude)
let markerStop = GMSMarker()
let markerImageStop = UIImage(named: "to_red_pin")!.withRenderingMode(.alwaysOriginal)
let markerViewStop = UIImageView(image: markerImageStop)
markerStop.position = stopOver
//marker.userData = dict
markerStop.iconView = markerViewStop
markerStop.map = vwMap
}
self.getPolylineRoute(from: source, to: destination)
}
func showPath(polyStr :String){
let path = GMSPath(fromEncodedPath: polyStr)
let polyline = GMSPolyline(path: path)
polyline.strokeWidth = 3.0
polyline.strokeColor = UIColor.black
polyline.map = vwMap // Your map view
}
I have tried lot of answer give below but not working for me. Please help me.
1st answer tried
2nd answer tried
3rd answer tried
you are setting wrong bounds so it is not showing on your map . I have tried your code it is working fine . Please change your bounds area as (0,0,0,0)
func getPolylineRoute(from source: CLLocationCoordinate2D, to destination: CLLocationCoordinate2D){
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
let url = URL(string: "https://maps.googleapis.com/maps/api/directions/json?origin=\(source.latitude),\(source.longitude)&destination=\(destination.latitude),\(destination.longitude)&sensor=true&mode=driving&key=AIzaSyAyU5txJ86b25-_l0DW-IldSKGGYqQJn3M")!
let task = session.dataTask(with: url, completionHandler: {
(data, response, error) in
DispatchQueue.main.async {
if error != nil {
print(error!.localizedDescription)
AppManager.dissmissHud()
}
else {
do {
if let json : [String:Any] = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String: Any]{
guard let routes = json["routes"] as? NSArray else {
DispatchQueue.main.async {
AppManager.dissmissHud()
}
return
}
if (routes.count > 0) {
let overview_polyline = routes[0] as? NSDictionary
let dictPolyline = overview_polyline?["overview_polyline"] as? NSDictionary
let points = dictPolyline?.object(forKey: "points") as? String
self.showPath(polyStr: points!)
DispatchQueue.main.async {
AppManager.dissmissHud()
let bounds = GMSCoordinateBounds(coordinate: source, coordinate: destination)
//below bounds change as 0 check it on full screen
let update = GMSCameraUpdate.fit(bounds, with: UIEdgeInsetsMake(0, 0, 0, 0))
self.vwMap!.moveCamera(update)
}
}
else {
DispatchQueue.main.async {
AppManager.dissmissHud()
}
}
}
}
catch {
print("error in JSONSerialization")
DispatchQueue.main.async {
AppManager.dissmissHud()
}
}
}
}
})
task.resume()
}
I did the same using this code have a look.
{let url = URL(string: "https://maps.googleapis.com/maps/api/directions/json?origin=\(self.currentLocation.coordinate.latitude),\(self.currentLocation.coordinate.longitude)&destination=\(33.6165),\(73.0962)&key=yourKey")
let request = URLRequest(url: url!)
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
let task = session.dataTask(with: request, completionHandler: {(data, response, error) in
// notice that I can omit the types of data, response and error
do{
let json = JSON(data!)
let errornum = json["error"]
if (errornum == true){
}else{
let routes = json["routes"].array
if routes != nil && (routes?.count)! > 0{
let overViewPolyLine = routes![0]["overview_polyline"]["points"].string
let dict = routes![0].dictionaryValue
let distance = dict["legs"]?[0]["distance"]
_ = distance?["text"].stringValue
let duaration = dict["legs"]?[0]["duration"]
_ = duaration?["text"].stringValue
//dict["legs"]?["distance"]["text"].stringValue
print(overViewPolyLine!)
if overViewPolyLine != nil{
DispatchQueue.main.async() {
self.addPolyLineWithEncodedStringInMap(encodedString: overViewPolyLine!)
}
}
}
}
and then
{
func addPolyLineWithEncodedStringInMap(encodedString: String) {
let path = GMSPath(fromEncodedPath: encodedString)!
let polyLine = GMSPolyline(path: path)
polyLine.strokeWidth = 5
polyLine.strokeColor = UIColor.yellow
polyLine.map = self.googleMapsView
let center = CLLocationCoordinate2D(latitude: 33.6165, longitude: 73.0962)
let marker = GMSMarker(position: center)
marker.map = self.googleMapsView
}
func decodePolyline(encodedString: String){
let polyline = Polyline(encodedPolyline: encodedString)
let decodedCoordinates: [CLLocationCoordinate2D]? = polyline.coordinates
for coordinate in decodedCoordinates! {
let marker = GMSMarker(position: coordinate)
marker.icon = UIImage(named: "mapPin")
marker.map = self.googleMapsView
}
}

I'm trying to draw polyline on coordinates(lat, long) coming from API using swift 3 in map kit but unable to make polyline

import UIKit
import MapKit
import CoreLocation
import AddressBook
class ViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
#IBOutlet weak var TheMap: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
zoomToRegion()
location()
}
func centerMapOnLocation(location: MKPointAnnotation, regionRadius: Double) {
let coordinateRegion = MKCoordinateRegionMakeWithDistance(location.coordinate,
regionRadius * 2.0, regionRadius * 2.0)
TheMap.setRegion(coordinateRegion, animated: true)
}
//MARK:- MapViewDelegate methods
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let polylineRenderer = MKPolylineRenderer(overlay: overlay)
if overlay is MKPolyline {
polylineRenderer.strokeColor = UIColor.blue
polylineRenderer.lineWidth = 5
}
return polylineRenderer
}
//MARK:- Zoom to region
func zoomToRegion() {
let location = CLLocationCoordinate2D(latitude: 28.618945, longitude: 77.377347400000005)
let region = MKCoordinateRegionMakeWithDistance(location, 5000.0, 7000.0)
TheMap.setRegion(region, animated: true)
}
// API CALL FUNCTION
func location() {
let user = "userid"
let password = "password"
let postString = ["empid":user, "date1": password]
var request = URLRequest(url:URL(string: "http://mydomainhere.com/airtel_hrm/webapi/api/getpunchdeytails")!)
request.httpMethod = "POST"
request.httpBody = try! JSONSerialization.data(withJSONObject: postString, options:.prettyPrinted)
let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?)in
if error != nil
{
print("error=\(error)")
return
}
do {
if let json = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String: Any],
let data = json["punchdetails"] as? [[String: Any]] {
//print(data)
for datas in data {
let lat = datas["punch_loc_lat"] as! String
let long = datas["punch_loc_long"] as! String
var annotations = [MKPointAnnotation]()
let latitude = CLLocationDegrees(lat)
let longitude = CLLocationDegrees(long)
let coordinate = CLLocationCoordinate2D(latitude: latitude!, longitude: longitude!)
let annotation = MKPointAnnotation()
annotation.coordinate = coordinate
annotations.append(annotation)
self.TheMap.addAnnotations(annotations)
self.TheMap.delegate = self
self.centerMapOnLocation(location: annotations[0], regionRadius: 2000.0)
// Connect all the mappoints using Poly line.
var points: [CLLocationCoordinate2D] = [coordinate] //[CLLocationCoordinate2D]()
for annotation in annotations {
points.append(annotation.coordinate)
}
print("this is points = \(points)")
let polyline = MKPolyline(coordinates: &points, count: points.count)
//self.TheMap.add(polyline)
} //for loop closed
}
} catch {
print(error)
}
}
task.resume()
}
}
I'm sure if you shared the results of the print statement, the problem would have been obvious. You're probably seeing lots of print statements. Bottom line, you should define your array outside of the for loop, only append values within the loop, and then add the polyline after the loop:
do {
if let json = try JSONSerialization.jsonObject(with: data!) as? [String: Any],
let data = json["punchdetails"] as? [[String: Any]] {
var annotations = [MKPointAnnotation]()
for datas in data {
let lat = datas["punch_loc_lat"] as! String
let long = datas["punch_loc_long"] as! String
let latitude = CLLocationDegrees(lat)
let longitude = CLLocationDegrees(long)
let coordinate = CLLocationCoordinate2D(latitude: latitude!, longitude: longitude!)
let annotation = MKPointAnnotation()
annotation.coordinate = coordinate
annotations.append(annotation)
}
self.TheMap.delegate = self // you really should do this in IB or, if you feel compelled to do it programmatically, in viewDidLoad
// Connect all the mappoints using Poly line.
let points = annotations.map { $0.coordinate }
print("this is points = \(points)")
let polyline = MKPolyline(coordinates: &points, count: points.count)
DispatchQueue.main.async {
self.centerMapOnLocation(location: annotations[0], regionRadius: 2000.0)
self.TheMap.addAnnotations(annotations)
self.TheMap.add(polyline)
}
}
} catch {
print(error)
}
Note, I'd also do all interaction with the map view from the main queue.

Ambiguous reference to member 'subscript' when using Google maps route

The below code works fine in Xcode 7.1 but when I upgrade to Xcode 8 and swift 3 I get this error, I had searched on internet for same problem in stackoverflow but none of them answer my question.
func drawRoute() {
clearRoute()
// self.routePolyline = nil
// self.routePolyline.map = nil
let route = mapTk.overviewPolyline!["points"] as String //error here
//var overViewPolyLine = routes![0]["overview_polyline"]["points"].string
let path: GMSPath = GMSPath(fromEncodedPath: route)
routePolyline = GMSPolyline(path: path)
routePolyline.map = viewMap
}
here is the class mapTK for more info:
import Foundation
import UIKit
import CoreLocation
import MapKit
///This Class is designed for the detail methods that contact google maps server like GeoCode, GetDirection and Calculate distance and time
class MapTK: NSObject {
let baseURLGeocode = "https://maps.googleapis.com/maps/api/geocode/json?"
var lookupAddressResults: Dictionary<NSObject, AnyObject>!
var fetchedFormattedAddress: String!
var fetchedAddressLongitude: Double!
var fetchedAddressLatitude: Double!
let baseURLDirections = "https://maps.googleapis.com/maps/api/directions/json?"
var selectedRoute: Dictionary<NSObject, AnyObject>!
var overviewPolyline: Dictionary<NSObject, AnyObject>!
var originCoordinate: CLLocationCoordinate2D!
var destinationCoordinate: CLLocationCoordinate2D!
var originAddress: String!
var destinationAddress: String!
var totalDistanceInMeters: UInt = 0
var totalDistance: String!
var totalDurationInSeconds: UInt = 0
var totalDuration: String!
/* private let errorDictionary = ["NOT_FOUND" : "At least one of the locations specified in the request's origin, destination, or waypoints could not be geocoded",
"ZERO_RESULTS":"No route could be found between the origin and destination",
"MAX_WAYPOINTS_EXCEEDED":"Too many waypointss were provided in the request The maximum allowed waypoints is 8, plus the origin, and destination",
"INVALID_REQUEST":"The provided request was invalid. Common causes of this status include an invalid parameter or parameter value",
"OVER_QUERY_LIMIT":"Service has received too many requests from your application within the allowed time period",
"REQUEST_DENIED":"Service denied use of the directions service by your application",
"UNKNOWN_ERROR":"Directions request could not be processed due to a server error. Please try again"]
*/
override init() {
super.init()
}
///the geocodeAddress is getting information from googlemaps with JSON it receive the geocode information
func geocodeAddress(address: String!, withCompletionHandler completionHandler: #escaping ((_: String, _: Bool) -> Void)) {
if let lookupAddress = address {
var geocodeURLString = baseURLGeocode + "address=" + lookupAddress
//geocodeURLString = geocodeURLString.stringByAddingPercentEncodingWithAllowedCharacters(.URLHostAllowedCharacterSet())!
let geocodeURL = NSURL(string: geocodeURLString)
// dispatch_Dispatch.Queue.mainasync(dispatch_get_main_queue(), { () -> Void in
DispatchQueue.main.async {
let geocodingResultsData = NSData(contentsOf: geocodeURL! as URL)
// let error: NSError?
do
{
let dictionary = try JSONSerialization.jsonObject(with: geocodingResultsData! as Data, options: JSONSerialization.ReadingOptions.mutableContainers) as? NSDictionary
// try NSJSONSerialization.JSONObjectWithData(directionsData!, options: NSJSONReadingOptions.MutableContainers) as? NSDictionary
/*
if (error != nil) {
print(error)
completionHandler(status: "", success: false)
}
*/
// else {
// Get the response status.
let status = dictionary?["status"]as! String
// let status = dictionary["status"] as! [AnyObject]
if status == "OK" {
let allResults = dictionary?["results"] as! Array<Dictionary<NSObject, AnyObject>>
self.lookupAddressResults = allResults[0]
// Keep the most important values.
self.fetchedFormattedAddress = self.lookupAddressResults["formatted_address"]? as? String
let geometry = self.lookupAddressResults["geometry"] as! Dictionary<NSObject, AnyObject>
self.fetchedAddressLongitude = ((geometry["location"] as! Dictionary<NSObject, AnyObject>)["lng"] as! NSNumber).doubleValue
self.fetchedAddressLatitude = ((geometry["location"] as! Dictionary<NSObject, AnyObject>)["lat"] as! NSNumber).doubleValue
completionHandler(status, true)
}
else {
completionHandler(status, false)
}
} catch let error as NSError {
print(error)
}
}
}
else {
completionHandler("No valid address.", false)
}
}
///getDirection method is using JSON to receive waypoints for making route for drawing polyline, as its name suggests it get the direciton inforamtion, first it send lat and lng the receive a dictionary and that dictionary give all the infrmation need to draw route like waypoints
func getDirections(origin: String!, destination: String!, waypoints: Array<String>!, travelMode: TravelModes!, completionHandler: #escaping ((_ status: String, _ success: Bool) -> Void)) {
if let originLocation = origin {
if let destinationLocation = destination {
var directionsURLString = baseURLDirections + "origin=" + originLocation + "&destination=" + destinationLocation //+ "&key=AIzaSyDsDqj0EMYZ-C4lGF3tmbntZtzurLl6_J4"
if let routeWaypoints = waypoints {
directionsURLString += "&waypoints=optimize:true"
for waypoint in routeWaypoints {
directionsURLString += "|" + waypoint
}
}
if let _ = travelMode {
var travelModeString = ""
switch travelMode.rawValue {
case TravelModes.walking.rawValue:
travelModeString = "walking"
case TravelModes.bicycling.rawValue:
travelModeString = "bicycling"
default:
travelModeString = "driving"
}
directionsURLString += "&mode=" + travelModeString
}
// directionsURLString = directionsURLString.stringByAddingPercentEncodingWithAllowedCharacters(NSUTF8StringEncoding)!
// directionsURLString = directionsURLString.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!
// directionsURLString = directionsURLString.stringByAddingPercentEncodingWithAllowedCharacters(.URLHostAllowedCharacterSet())!
/// directionsURLString = directionsURLString.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!
directionsURLString = directionsURLString.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!
//let encodedHost = unencodedHost.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)
//var url = NSURL(urlString.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet()))
//let savePath = (documentDirectory as NSString).stringByAppendingPathComponent("mergeVideo-\(date).mov")
let directionsURL = NSURL(string: directionsURLString)
// DispatchQueue.main.asynchronously(execute: { () -> Void in
DispatchQueue.main.async {
let directionsData = NSData(contentsOf: directionsURL! as URL)
// var error: NSError?
do {
let dictionary = try JSONSerialization.jsonObject(with: directionsData! as Data, options: JSONSerialization.ReadingOptions.mutableContainers) as? NSDictionary
/*
if (error != nil) {
print(error)
completionHandler(status: "", success: false)
}
*/
// else {
let status = dictionary?["status"] as! String
if status == "OK" {
self.selectedRoute = (dictionary?["routes"] as! Array<Dictionary<NSObject, AnyObject>>)[0]
self.overviewPolyline = self.selectedRoute["overview_polyline"] as! Dictionary<NSObject, AnyObject>
let legs = self.selectedRoute["legs"] as! Array<Dictionary<NSObject, AnyObject>>
let startLocationDictionary = legs[0]["start_location"] as! Dictionary<NSObject, AnyObject>
self.originCoordinate = CLLocationCoordinate2DMake(startLocationDictionary["lat"] as! Double, startLocationDictionary["lng"] as! Double)
let endLocationDictionary = legs[legs.count - 1]["end_location"]as! Dictionary<NSObject, AnyObject>
self.destinationCoordinate = CLLocationCoordinate2DMake(endLocationDictionary["lat"] as! Double, endLocationDictionary["lng"] as! Double)
self.originAddress = legs[0]["start_address"] as! String
self.destinationAddress = legs[legs.count - 1]["end_address"] as! String
self.calculateTotalDistanceAndDuration()
completionHandler(status, true)
}
else {
completionHandler(status, false)
}
} catch let error as NSError {
print(error)
}
}
}
else {
completionHandler("Destination is nil.", false)
}
}
else {
completionHandler("Origin is nil", false)
}
}
/// this method below is for the calculation of Distance adn Duration of each location for travel
func calculateTotalDistanceAndDuration() {
let legs = self.selectedRoute["legs"] as! Array<NSDictionary>
totalDistanceInMeters = 0
totalDurationInSeconds = 0
for leg in legs {
totalDistanceInMeters += (leg["distance"] as! Dictionary<NSObject, AnyObject>)["value"] as! UInt
totalDurationInSeconds += (leg["duration"]as! Dictionary<NSObject, AnyObject>)["value"] as! UInt
}
let distanceInKilometers: Double = Double(totalDistanceInMeters / 1000)
totalDistance = "Total Distance: \(distanceInKilometers) Km"
let mins = totalDurationInSeconds / 60
let hours = mins / 60
let days = hours / 24
let remainingHours = hours % 24
let remainingMins = mins % 60
let remainingSecs = totalDurationInSeconds % 60
totalDuration = "Duration: \(days) d, \(remainingHours) h, \(remainingMins) mins, \(remainingSecs) secs"
}
}
Use
Dictionary<String, Any>
as JSON dictionary type.
JSON keys are required to be String anyway and
AnyObject has been changed to Any in Swift 3.
Edit :
I recommend to use a type alias for the JSON dictionary type
typealias JSONObject = [String:Any] // synonym of Dictionary<String, Any>
Then you can write
var lookupAddressResults: JSONObject!
...
var selectedRoute: JSONObject!
var overviewPolyline: JSONObject!
and the parsing code
...
if status == "OK" {
let allResults = dictionary?["results"] as! Array<JSONObject>
self.lookupAddressResults = allResults[0]
// Keep the most important values.
self.fetchedFormattedAddress = self.lookupAddressResults["formatted_address"] as? String
let geometry = self.lookupAddressResults["geometry"] as! JSONObject
self.fetchedAddressLongitude = ((geometry["location"] as! JSONObject)["lng"] as! NSNumber).doubleValue
self.fetchedAddressLatitude = ((geometry["location"] as! JSONObject)["lat"] as! NSNumber).doubleValue
completionHandler(status, true)
}
...
You can also replace
( ... as! NSNumber).doubleValue
with
... as! Double

Display route on map in Swift

I am trying to draw the route between two points on Apple map (Swift code).
The following structure is used to store the coordinates
struct GeoLocation {
var latitude: Double
var longitude: Double
func distanceBetween(other: GeoLocation) -> Double {
let locationA = CLLocation(latitude: self.latitude, longitude: self.longitude)
let locationB = CLLocation(latitude: other.latitude, longitude: other.longitude)
return locationA.distanceFromLocation(locationB)
}
}
self.foundLocations - is an array of these structures
In the custom class I recieve the coordinates of the points on the map.
var coordinates = self.foundLocations.map{$0.coordinate}
Then I draw the route on the map
self.polyline = MKPolyline(coordinates: &coordinates, count: coordinates.count)
self.mapView.addOverlay(self.polyline, level: MKOverlayLevel.AboveRoads)
To draw the route I use the following method from MKMapViewDelegate
func mapView(mapView: MKMapView!, rendererForOverlay overlay: MKOverlay!) -> MKOverlayRenderer! {
if let polylineOverlay = overlay as? MKPolyline {
let render = MKPolylineRenderer(polyline: polylineOverlay)
render.strokeColor = UIColor.blueColor()
return render
}
return nil
}
Instead of the actual route laying on roads I get just a straight line between two points.
How can I display the actual route?
You actually have to fetch the route from Apple's maps' server using calculateDirectionsWithCompletionHandler.
First create the relevant MKMapItems for both the source and destination, ex:
let geocoder = CLGeocoder()
let location = CLLocation(latitude: sourceLatitude, longitude: sourceLongitude)
geocoder.reverseGeocodeLocation(location, completionHandler: {
(placemarks:[AnyObject]?, error:NSError?) -> Void in
if placemarks?.count > 0 {
if let placemark: MKPlacemark = placemarks![0] as? MKPlacemark {
self.source = MKMapItem(placemark: placemark)
}
}
})
(Repeat for destination.)
Then fetch the MKRoute, ex:
let request:MKDirectionsRequest = MKDirectionsRequest()
// source and destination are the relevant MKMapItems
request.setSource(source)
request.setDestination(destination)
// Specify the transportation type
request.transportType = MKDirectionsTransportType.Automobile;
// If you're open to getting more than one route,
// requestsAlternateRoutes = true; else requestsAlternateRoutes = false;
request.requestsAlternateRoutes = true
let directions = MKDirections(request: request)
directions.calculateDirectionsWithCompletionHandler ({
(response: MKDirectionsResponse?, error: NSError?) in
if error == nil {
self.directionsResponse = response
// Get whichever currentRoute you'd like, ex. 0
self.route = directionsResponse.routes[currentRoute] as MKRoute
}
})
Then after retrieving the MKRoute, you can add the polyline to the map like so:
mapView.addOverlay(route.polyline, level: MKOverlayLevel.AboveRoads)
Swift 3 and reusable conversion of Lyndsey Scott's answer:
final class Route {
static func getRouteFor(
source: CLLocationCoordinate2D,
destination: CLLocationCoordinate2D,
completion: #escaping (
_ route: MKRoute?,
_ error: String?)->()
) {
let sourceLocation = CLLocation(
latitude: source.latitude,
longitude: source.longitude
)
let destinationLocation = CLLocation(
latitude: destination.latitude,
longitude: destination.longitude
)
let request = MKDirectionsRequest()
self.getMapItemFor(location: sourceLocation) { sourceItem, error in
if let e = error {
completion(nil, e)
}
if let s = sourceItem {
self.getMapItemFor(location: destinationLocation) { destinationItem, error in
if let e = error {
completion(nil, e)
}
if let d = destinationItem {
request.source = s
request.destination = d
request.transportType = .walking
let directions = MKDirections(request: request)
directions.calculate(completionHandler: { response, error in
if let r = response {
let route = r.routes[0]
completion(route, nil)
}
})
}
}
}
}
}
static func getMapItemFor(
location: CLLocation,
completion: #escaping (
_ placemark: MKMapItem?,
_ error: String?)->()
) {
let geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(location) { placemark, error in
if let e = error {
completion(nil, e.localizedDescription)
}
if let p = placemark {
if p.count < 1 {
completion(nil, "placemark count = 0")
} else {
if let mark = p[0] as? MKPlacemark {
completion(MKMapItem(placemark: mark), nil)
}
}
}
}
}
}
Usage:
Route.getRouteFor(source: CLLocationCoordinate2D, destination: CLLocationCoordinate2D) { (MKRoute?, String?) in
<#code#>
}

Resources