Calculate ETA in Swift - ios

I'm attempting to calculate the estimated travel time (walking) between two locations in swift, the user to an annotation.
Here is my current code, it does not throw any errors that crash the program but only returns "Error while requesting ETA"
import UIKit
import MapKit
class LocationObjects: NSObject, MKAnnotation {
/*let userView = CLLocation(latitude: mapView.centerCoordinate.latitude, longitude: mapView.centerCoordinate.longitude)
let annotationPoint = CLLocation(latitude: annotation.coordinate.latitude, longitude: annotation.coordinate.longitude)
let distance = userView.distance(from: annotationPoint)*/
var identifier = "bathroom location"
var title: String?
var coordinate: CLLocationCoordinate2D
var distance: Double
var phoneNumber: String
var addressFinished: String
var estimatedTravelTime: String
// Request ETA function
// Uses user location and destination to send request
// For Estimated time of arrival
class func requestETA(userCLLocation: CLLocation, coordinate: CLLocationCoordinate2D) -> String {
var travelTime = String()
let request = MKDirectionsRequest()
/* Source MKMapItem */
let sourceItem = MKMapItem(placemark: MKPlacemark(coordinate: userCLLocation.coordinate, addressDictionary: nil))
request.source = sourceItem
/* Destination MKMapItem */
let destinationItem = MKMapItem(placemark: MKPlacemark(coordinate: coordinate, addressDictionary: nil))
request.destination = destinationItem
request.requestsAlternateRoutes = false
// Looking for walking directions
request.transportType = MKDirectionsTransportType.walking
// You use the MKDirectionsRequest object constructed above to initialise an MKDirections object
let directions = MKDirections(request: request)
directions.calculateETA { (etaResponse, error) -> Void in
if let error = error {
print("Error while requesting ETA : \(error.localizedDescription)")
travelTime = "Not Available"
}else{
print("No error requesting ETA")
travelTime = "\(Int((etaResponse?.expectedTravelTime)!/60)) min"
}
}
return travelTime
}
init(name:String,lat:CLLocationDegrees,long:CLLocationDegrees,userCLLocation:CLLocation, phone:String, address:String){
title = name
coordinate = CLLocationCoordinate2DMake(lat, long)
distance = Double(String(format: "%.2f", (userCLLocation.distance(from: CLLocation(latitude: lat, longitude: long)))*0.000621371))!
phoneNumber = phone
addressFinished = address
estimatedTravelTime = LocationObjects.requestETA(userCLLocation: userCLLocation, coordinate: coordinate)
}
}
The console looks something like this for an exceedingly long amount of lines
2017-05-26 13:53:51.583 Toilet Locator[2568:181698] didFailWithError Error Domain=GEOErrorDomain Code=-3 "(null)" UserInfo={GEORequestThrottleStateLevel=0, GEORequestThrottleStateResetTimeRemaining=34.019131004810333}
2017-05-26 13:53:51.584 Toilet Locator[2568:181698] didFailWithError Error Domain=GEOErrorDomain Code=-3 "(null)" UserInfo={GEORequestThrottleStateLevel=0, GEORequestThrottleStateResetTimeRemaining=34.018206000328064}
Error while requesting ETA : Directions Not Available
Error while requesting ETA : Directions Not Available
I'm hoping for some guidance, thank you all!
Note: ViewController calls this class to be made many times in a row so not sure if that would cause Apple to reject ETA requests?

Related

Access to the coordinates from an address swift

Hi how can I access to the longitude and latitude parameters outside of this block ?
let geocoder = CLGeocoder()
geocoder.geocodeAddressString(address) { placemarks, error in
let placemark = placemarks?.first
let lat = placemark?.location?.coordinate.latitude
let lon = placemark?.location?.coordinate.longitude
print("Lat: \(lat), Lon: \(lon)")
}
I want to save this two params as Float or String in firebase. Thanks
You can create a function like this:
func getLatAndLong(address : String, competion : #escaping (CLLocationCoordinate2D?) -> ()) {
let geocoder = CLGeocoder()
geocoder.geocodeAddressString(address) { placemarks, error in
let placemark = placemarks?.first
let lat = placemark?.location?.coordinate.latitude
let lon = placemark?.location?.coordinate.longitude
print("Lat: \(lat), Lon: \(lon)")
competion(placemark?.location?.coordinate)
}
}
and then call it as :
var location : CLLocationCoordinate2D?
getLatAndLong(address: "pass your address here") { (coordinates) in
self.location = coordinates
}
Now your location variable holds the value.
you can use it to get latitide and longitude
let latitide = location?.latitude
let longitude = location?.longitude
Option 2
You can declare a variable in class and update its you value once you get the coordinates. I would suggest using the above approach as geocoder runs asynchronously. Its better to use a completion handler to know when the geoCoder has finished executing the task and has got the location.
var latitude = 0.0 //
var longitude = 0.0
let geocoder = CLGeocoder()
geocoder.geocodeAddressString("address") { placemarks, error in
let placemark = placemarks?.first
self.latitude = placemark?.location?.coordinate.latitude ?? 0.0
self.longitude = placemark?.location?.coordinate.longitude ?? 0.0
}

How to get directions from user location to another specific location?

I have 2 locations in the map. First one is users current location with blue dot placemark and the second one is specified restaurant location near of the users current location with red dot placemark. I want to get directions from user's current location to restaurant location. I am getting directions when I define latitude and longitude values like latitude: 38.00001 and longitude: 27.08980.
Also I am getting each restaurants location from api to red dot placemark but do not get directions from user current location to restaurant location.
I am getting restaurant location with these codes:
var viewModel: DetailsViewModel? {
didSet {
updateView()
}
willSet {
updateView()
}
}
func updateView() {
if let viewModel = viewModel {
detailsFoodView?.priceLabel?.text = viewModel.price
detailsFoodView?.hoursLabel?.text = viewModel.isOpen
detailsFoodView?.locationLabel?.text = viewModel.phoneNumber
detailsFoodView?.ratingsLabel?.text = viewModel.rating
detailsFoodView?.collectionView?.reloadData()
centerMap(for: viewModel.coordinate)
print("detail restaurant destination : \(viewModel.coordinate)")
title = viewModel.name
print("title: \(title)")
}
}
func centerMap(for coordinate: CLLocationCoordinate2D) {
let region = MKCoordinateRegion(center: coordinate, latitudinalMeters: 100, longitudinalMeters: 100)
let annotation = MKPointAnnotation()
annotation.coordinate = coordinate
detailsFoodView?.mapView?.addAnnotation(annotation)
detailsFoodView?.mapView?.setRegion(region, animated: true)
}
}
And in viewDidLoad() getting directions like:
let sourceCoordinates = locationManager.location?.coordinate
Problem is here
I do not get each restaurant's latitude and longitude values to destCoordinates. Normally, I get with viewModel.coordinate.
But if I use
let destCoordinates = CLLocationCoordinate2D(latitude: viewModel?.coordinate.latitude, longitude: viewModel?.coordinate.longitude)
Instead of:
let destCoordinates = CLLocationCoordinate2D(latitude: 38.420183, longitude: 27.205559)
I am getting latitude and longitude values nil or zero.
let sourcePlaceMark = MKPlacemark(coordinate: sourceCoordinates!)
let destPlaceMark = MKPlacemark(coordinate: destCoordinates)
let sourceItem = MKMapItem(placemark: sourcePlaceMark)
let destItem = MKMapItem(placemark: destPlaceMark)
let directionRequest = MKDirections.Request()
directionRequest.source = sourceItem
directionRequest.destination = destItem
directionRequest.transportType = .automobile
let directions = MKDirections(request: directionRequest)
directions.calculate(completionHandler: {response, error in
guard let response = response else {
if error != nil {
print("Something went wrong!")
}
return
}
let route = response.routes[0]
self.detailsFoodView?.mapView?.addOverlay(route.polyline, level: .aboveRoads)
let rect = route.polyline.boundingMapRect
self.detailsFoodView?.mapView?.setRegion(MKCoordinateRegion(rect), animated: true)
Expected result is current user place as blue dot placemark to red dot placemark with red line as you can see in the screen shot.
Image:

How to calculate distance between two locations using google api in swift 4

I am currently working on an IOS project in which i have to calculate distance between two location. I have done without using google but i want to use google api to get accurate distance i am sharing my code here
let myLocation = CLLocation(latitude: CLLocationDegrees(latittude[indexPath.row])!, longitude: CLLocationDegrees(longittude[indexPath.row])!)
let lat = UserDefaults.standard.string(forKey: "lat") ?? ""
let long = UserDefaults.standard.string(forKey: "long") ?? ""
let myBuddysLocation = CLLocation(latitude: CLLocationDegrees(lat)!, longitude: CLLocationDegrees(long)!)
Use distance function of CoreLocation Framework,
var startLocation = CLLocation(latitude: startLatitude, longitude: startLongitude)
var endLocation = CLLocation(latitude: endLatitude, longitude: endLongitude)
var distance: CLLocationDistance = startLocation.distance(from: endLocation)
Swift 5+:
As far as I know, there are two ways to find the distance.
If you are looking for driving distance, you can always use MKDirections.
Here is the code for finding driving distance (You can also find walking, and transit distance by changing transport type).
let sourceP = CLLocationCoordinate2DMake( sourceLat, sourceLong)
let destP = CLLocationCoordinate2DMake( desLat, desLong)
let source = MKPlacemark(coordinate: sourceP)
let destination = MKPlacemark(coordinate: destP)
let request = MKDirections.Request()
request.source = MKMapItem(placemark: source)
request.destination = MKMapItem(placemark: destination)
// Specify the transportation type
request.transportType = MKDirectionsTransportType.automobile;
// If you want only the shortest route, set this to a false
request.requestsAlternateRoutes = true
let directions = MKDirections(request: request)
// Now we have the routes, we can calculate the distance using
directions.calculate { (response, error) in
if let response = response, let route = response.routes.first {
print(route.distance) //This will return distance in meters
}
}
If you are only looking for air distance/bird's eye distance/coordinate distance, you can use this code:
let sourceP = CLLocation(latitude: sourceLat, longitude: sourceLong)
let desP = CLLocation(latitude: desLat, longitude: desLong))
let distanceInMeter = sourceP.distance(from: desP)

Calculate distance between my location and a MapKit pin on Swift

I need your help, I'm working on an App where I have some pins (locations) and what I want is to get the distance between each one and my location. My code is the following
let annotation = MKPointAnnotation()
let annotationTwo = MKPointAnnotation()
let saintPaulHospitalBC = MKPointAnnotation()
override func viewDidLoad() {
super.viewDidLoad()
mapita.showsUserLocation = true // Mapita is the name of the MapView.
annotation.coordinate = CLLocationCoordinate2D(latitude: 25.647399800, longitude: -100.334304500)
mapita.addAnnotation(annotation)
annotationTwo.coordinate = CLLocationCoordinate2D(latitude: 25.589339000, longitude: -100.257724800)
mapita.addAnnotation(annotationTwo)
saintPaulHospitalBC.coordinate = CLLocationCoordinate2D(latitude: 49.280524700, longitude: -123.128232600)
mapita.addAnnotation(SaintPaulHospitalBC)
}
When I run the code, the map shows the pins, but what else can I do to start calculating the distance? Thank you!
You're gonna have to convert the coordinates of your annotations to CLLocation types, then get the distance between them. To ignore the height of the coordinates, as they are 2D, just use the latitude and longitude properties of the 2D coordinates, like so:
let loc1 = CLLocation(latitude: coord1.latitude, longitude: coord1.longitude)
However, CLLocation has some other properties such as speed and height, so if you want to factor those in you'll have to give more information. To find the distance between the two locations, do this:
let distance = loc1.distance(from: loc2)
This will give your answer as a double in meters.
Create a helper function to compute the distance between the user location and a given MKPointAnnotation pin:
/// Returns the distance (in meters) from the
/// user's location to the specified point.
private func userDistance(from point: MKPointAnnotation) -> Double? {
guard let userLocation = mapita.userLocation.location else {
return nil // User location unknown!
}
let pointLocation = CLLocation(
latitude: point.coordinate.latitude,
longitude: point.coordinate.longitude
)
return userLocation.distance(from: pointLocation)
}
Finally, to get the user distance to Saint Paul hospital:
if let distance = userDistance(from: saintPaulHospitalBC) {
// Use distance here...
}
Geolocation tracking latency. There is a catch though: the user distance might not always be available at first, since MapKit/CoreLocation geolocation tracking might still be running in the background.
One way around this, is to conform to the MKMapViewDelegate protocol and wait for the mapView(_:didUpdate:) callback before finally computing your distances.
To put it in perspective, you need to first specify what "distance" are you looking for. If you are looking for simple Euclidean Distance then any of the other answers or using distanceFromLocation would work. According to Apple's documentaion on distanceFromLocation
This method measures the distance between the two locations by tracing
a line between them that follows the curvature of the Earth. The
resulting arc is a smooth curve and does not take into account
specific altitude changes between the two locations.
This means, that the distance derived using this method will not be the actual route/transportation distance between two points.
If that is what you are looking for then head over to the answer I linked above, if not then keep reading (but either way, I encourage you to read the whole post :).
If you are looking for "route" distance (drivable, walkable etc.) between your location and the other annotations in the map, it's going to take little more work using MKRoute object. To be more specific you need to first have access to the MKMapItem objects of each of your annotations and then a custom method like below would be able to get the route info between two MapItem objects.
Note - if you don't have MapItems then you can create them just using the coordinates of each of your annotations, like so
ley myCoordinates CLLocationCoordinate2D(latitude: 25.647399800, longitude: -100.334304500)
let myPlacemark = MKPlacemark(coordinate: myCoordinates)
let myMapItem = MKMapItem(placemark: myPlacemark)
Define an MKRoute variable globally in your class (or ViewController class). This var will hold the calculated Route information between two points.
var route: MKRoute!
and then
func getDistanceToDestination(srcMapItem srcmapItem: MKMapItem, destMapItem destmapItem: MKMapItem){
let request = MKDirectionsRequest() //create a direction request object
request.source = srcmapItem //this is the source location mapItem object
request.destination = destmapItem //this is the destination location mapItem object
request.transportType = MKDirectionsTransportType.automobile //define the transportation method
let directions = MKDirections(request: request) //request directions
directions.calculate { (response, error) in
guard let response = response else {
print(error.debugDescription)
return
}
self.route = response.routes[0] //get the routes, could be multiple routes in the routes[] array but usually [0] is the best route
}
}
Usage would be
self.getDistanceToDestination(srcMapItem: yourSourceMapItemObj, destMapItem: yourDestinationMapitemObj)
where yourSourceMapItemObj and yourDestinationMapitemObj are two MapItem objects aka source and destination points.
And then you can access the distance using self.route.distance to get the distance of the first best route returned by MKRoute. There are a whole bunch of other properties for the MKRoute object route which you can use as well to display/calculate other things, and I encourage you to take a look at those too. You can use the function above to also draw a ployLine i.e. a line showing the route between the two locations in the MapView just by adding self.mapView.add(self.route.polyline) in the end of the custom method above and then use the below MKMapViewDelegate function below to render the polyline.
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let linerenderer = MKPolylineRenderer(overlay: self.route.polyline)
linerenderer.strokeColor = .blue
linerenderer.lineWidth = 3.5
return linerenderer
}
And finally, make sure your class (or your class extension) complies to CLLocationManagerDelegate and MKMapViewDelegate protocols and mapview delegate pointed to self (which I assume you already do) in order for everything above to work.
Its easy try my code below.
Don't forget to import CoreLocation or MapKit, hope it helps you
func calculateDistancefrom(sourceLocation: MKMapItem, destinationLocation: MKMapItem, doneSearching: #escaping (_ expectedTravelTim: TimeInterval) -> Void) {
let request: MKDirectionsRequest = MKDirectionsRequest()
request.source = sourceLocation
request.destination = destinationLocation
request.requestsAlternateRoutes = true
request.transportType = .automobile
let directions = MKDirections(request: request)
directions.calculate { (directions, error) in
if var routeResponse = directions?.routes {
routeResponse.sort(by: {$0.expectedTravelTime <
$1.expectedTravelTime})
let quickestRouteForSegment: MKRoute = routeResponse[0]
doneSearching(quickestRouteForSegment.distance)
}
}
}
func getDistance(lat: Double, lon: Double, completionHandler: #escaping (_ distance: Int) -> Void) {
let destinationItem = MKMapItem(placemark: MKPlacemark(coordinate: CLLocationCoordinate2DMake(lat, lon)))
guard let currentLocation = self.locationManager?.location else { return }
let sourceItem = MKMapItem(placemark: MKPlacemark(coordinate: currentLocation.coordinate))
self.calculateDistancefrom(sourceLocation: sourceItem, destinationLocation: destinationItem, doneSearching: { distance in
completionHandler(distance)
})
}
//Thereafter get the distance in meters by calling
self.getDistance(lat: yourLat, lon: YourLon) { distance in
}
//you can divide by 1000 to convert to KM... .etc
Using MapKit & Swift 5
Calculate distance between two location location
Sample Function : I have tested in Google Map as well as Apple Map
let startLocation : CLLocation = CLLocation.init(latitude: 23.0952779, longitude: 72.5274129)
let endLocation : CLLocation = CLLocation.init(latitude: 23.0981711, longitude: 72.5294229)
let distance = startLocation.distance(from: endLocation)
self.getDistance(departureDate: Date().adjust(hour: 8, minute: 0, second: 0, day: 0, month: 0), arrivalDate: Date().adjust(hour: 8, minute: 10, second: 0, day: 0, month: 0), startLocation: startLocation, endLocation: endLocation) { (distanceInMeters) in
print("fake distance: \(distance)")
let fakedistanceInMeter = Measurement(value: distance, unit: UnitLength.meters)
let fakedistanceInKM = fakedistanceInMeter.converted(to: UnitLength.kilometers).value
let fakedistanceInMiles = fakedistanceInMeter.converted(to: UnitLength.miles).value
print("fakedistanceInKM :\(fakedistanceInKM)")
print("fakedistanceInMiles :\(fakedistanceInMiles)")
print("actualDistance : \(distanceInMeters)")
let distanceInMeter = Measurement(value: distanceInMeters, unit: UnitLength.meters)
let distanceInKM = distanceInMeter.converted(to: UnitLength.kilometers).value
let distanceInMiles = distanceInMeter.converted(to: UnitLength.miles).value
print("distanceInKM :\(distanceInKM)")
print("distanceInMiles :\(distanceInMiles)")
}
Use of functions
self.getDistance(departureDate: trip.departure.dateTime, arrivalDate: trip.arrival.dateTime, startLocation: startLocation, endLocation: endLocation) { (actualDistance) in
print("actualDistance : \(actualDistance)")
}
I am improved above function and added code here, I hope it will help someone.
func calculateDistancefrom(departureDate: Date, arrivalDate: Date, sourceLocation: MKMapItem, destinationLocation: MKMapItem, doneSearching: #escaping (_ distance: CLLocationDistance) -> Void) {
let request: MKDirections.Request = MKDirections.Request()
request.departureDate = departureDate
request.arrivalDate = arrivalDate
request.source = sourceLocation
request.destination = destinationLocation
request.requestsAlternateRoutes = true
request.transportType = .automobile
let directions = MKDirections(request: request)
directions.calculate { (directions, error) in
if var routeResponse = directions?.routes {
routeResponse.sort(by: {$0.expectedTravelTime <
$1.expectedTravelTime})
let quickestRouteForSegment: MKRoute = routeResponse[0]
doneSearching(quickestRouteForSegment.distance)
}
}
}
func getDistance(departureDate: Date, arrivalDate: Date, startLocation : CLLocation, endLocation : CLLocation, completionHandler: #escaping (_ distance: CLLocationDistance) -> Void) {
let destinationItem = MKMapItem(placemark: MKPlacemark(coordinate: startLocation.coordinate))
let sourceItem = MKMapItem(placemark: MKPlacemark(coordinate: endLocation.coordinate))
self.calculateDistancefrom(departureDate: departureDate, arrivalDate: arrivalDate, sourceLocation: sourceItem, destinationLocation: destinationItem, doneSearching: { distance in
completionHandler(distance)
})
}

Can I get a store name/restaurant name with mapkit?(swift)

I have a mapview and I added a method to drop a pin on the location where the user had pressed. The callout shows the address of the location as shown on the image.
screenshot of my mapview with annotation pin and callout view.
And my code is as following:
func onTapGestureRecognized(sender: UILongPressGestureRecognizer) {
self.mapView.removeAnnotations(mapView.annotations)
let location = tapRecognizer.location(in: mapView)
let coordinate = mapView.convert(location,toCoordinateFrom: mapView)
let getLat: CLLocationDegrees = coordinate.latitude
let getLon: CLLocationDegrees = coordinate.longitude
let theLocation: CLLocation = CLLocation(latitude: getLat, longitude: getLon)
let geoCoder = CLGeocoder()
geoCoder.reverseGeocodeLocation(theLocation, completionHandler: { (placemarks, error) -> Void in
// Place details
var placeMark: CLPlacemark!
placeMark = placemarks?[0]
var theLocationName = ""
var theStreetNumber = ""
var theStreet = ""
var theCity = ""
var theZip = ""
var theCountry = ""
// Address dictionary
print(placeMark.addressDictionary as Any)
// Location name
if let locationName = placeMark.name{
theLocationName = locationName
}
if let streetNumber = placeMark.subThoroughfare{
theStreetNumber = streetNumber
}
// Street address
if let street = placeMark.thoroughfare {
theStreet = street
}
// City
if let city = placeMark.locality {
theCity = city
}
// Zip code
if let zip = placeMark.postalCode{
theZip = zip
}
// Country
if let country = placeMark.isoCountryCode{
theCountry = country
}
let annotation = MKPointAnnotation()
annotation.title = theLocationName
annotation.subtitle = theStreetNumber + " " + theStreet + ", " + theCity + ", " + theCountry + ", " + theZip
if let location = placeMark.location {
annotation.coordinate = location.coordinate
// Display the annotation
self.mapView.showAnnotations([annotation], animated: true)
}
})
}
As you can see, when I try to get the location name by calling the line (((( if let locationName = placeMark.name )))), I can only get the address: "5197 Yonge St", instead of the restaurant name : " Pho 88 Restaurant ".
Can anyone tell me where I did wrong? or is it simply cannot be achieved? Thanks!
I can't give you a complete answer, but I may be able to point you in the right direction. As far as I can see, you will only ever get a single entry returned for placemarks, but you can get a more complete list using MKLocalSearchRequest. the challenge is going to be how you match up the returned values to exactly which one you want - maybe you have to ask the user to select from a short list? Also, I think you need to specify which type of establishment you're searching for. Here's something you could include within your completion handler above
let request = MKLocalSearchRequest()
request.naturalLanguageQuery = "restaurant" // or whatever you're searching for
request.region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: getLat, longitude: getLon), span: self.mapView.region.span)
let search = MKLocalSearch(request: request)
search.start { response, error in
guard let response = response else {
print("There was an error searching for: \(request.naturalLanguageQuery) error: \(error)")
return
}
print("There are \(response.mapItems.count)")
for item in response.mapItems {
// You may be able to match the address to what the geoCode gives you
// or present the user with a list of options
print("\(item.name), \(item.placemark)")
}
}
When I was testing this, the addresses didn't always match up, even when zoomed in - so that geoCoder might give me 1-3 Some Street while the MKLocalSearchRequest returned a restaurant at 3 Some Street

Resources