I want to show my total distance from my current location to another point and I want the distance shows in subtitle of Annotation.
Here is my code but it doesn't work :
func shopDeal () {
var inKm = ""
let locationsq =
[["title": "Shop Deal","subtitle": "\(inKm) km", "latitude": 31.352792, "longitude": 74.253201],
["title": "Shop Deal", "subtitle": "\(inKm) km", "latitude": 31.403563, "longitude": 74.258200]]
// Span
for location in locationsq {
let annotation = MKPointAnnotation()
annotation.title = location["title"] as? String
annotation.subtitle = location["subtitle"] as? String
annotation.coordinate = CLLocationCoordinate2D(latitude: location["latitude"] as! Double, longitude: location["longitude"] as! Double)
mapView.addAnnotation(annotation)
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let myCurrentLoc = locations[locations.count - 1]
let coor1 = CLLocation(latitude: annotation.coordinate.latitude, longitude: annotation.coordinate.longitude)
let coor2 = CLLocation (latitude: myCurrentLoc.coordinate.latitude, longitude: myCurrentLoc.coordinate.longitude )
let distanceInMeters:CLLocationDistance = coor1.distanceFromLocation(coor2)
inKm = String(format: "%.2f", distanceInMeters / 1000)
print("\(inKm) km")
}
}
mapView.delegate = self
}
Any help is highly appreciated. Thanks in advance
Finding Distance between two points(LAT and LONG) on Map :
let coordinate1 = CLLocation(latitude: 5.0, longitude: 5.0)
let coordinate2 = CLLocation(latitude: 5.0, longitude: 3.0)
//Decalare distanceInMeters as global variables so that you can show distance on subtitles
let distanceInMeters = coordinate1.distance(from: coordinate2)
Now if you want to show distance in subtitle of annotation then :
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation {
return nil
}
let reuseId = "pinIdentifier"
var pinView = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId) as? MKPinAnnotationView
if pinView == nil {
pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
pinView!.canShowCallout = true
}
else {
pinView!.annotation = annotation
}
//Adding Subtitle text
let subtitleView = UILabel()
//Decalare distanceInMeters as global variables so that you can show distance on subtitles
subtitleView.text = "\(distanceInMeters)"
pinView!.detailCalloutAccessoryView = subtitleView
return pinView
}
Feel free to comment if further any issue
Try this!
// 1. Get Source & Destination coordinates (Hard-cored here)
let locSource : CLLocation = CLLocation.init(latitude: 17.428031, longitude: 78.377837)
let locDestination : CLLocation = CLLocation.init(latitude: 17.441879, longitude: 78.429990)
// 2. Find distance between the Source & Destination
let nDistanceInMeters : CLLocationDistance = locSource.distance(from: locDestination)
let nDistanceInKiloMeters : CLLocationDistance = nDistanceInMeters / 1000.0;
// 3. Convert to String
let strDistanceInMeters : String = String.init(format: "%f", nDistanceInMeters)
let strDistanceInKM : String = String.init(format: "%f", nDistanceInKiloMeters)
// 4. Assign the calculated distance to your label,
// Probably in mapView(mapView:viewForAnnotation annotation:
lblDistanceLabelOnAnnotationView.text = strDistanceInKM
Hope that helps!
Related
Currently I can render the spheres using latitude and longitude in ARKit Geolocation Tracking , can anyone please guide me how can I draw polyline between 2 CLLocation in ARKit .
here is a full code to create poly line between two points and also set a width and color of that poly line
var locManager = CLLocationManager()
var currentLocation: CLLocation!
let annotation = MKPointAnnotation()
let annotation2 = MKPointAnnotation()
// MARK:- DRIVER -
var driverLatitute:String!
var driverLongitude:String!
// MARK:- RESTAURANT -
var restaurantLatitude:String!
var restaurantLongitude:String!
IN VIEW DID LOAD
// MARK:- 1 ( MAP ) -
self.locManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
self.locManager.delegate = self
self.locManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
self.locManager.startUpdatingLocation()
print("UPDATE UPDATE")
}
if (CLLocationManager.authorizationStatus() == CLAuthorizationStatus.authorizedWhenInUse ||
CLLocationManager.authorizationStatus() == CLAuthorizationStatus.authorizedAlways) {
print("")
}
DELEGATE METHODS
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
//print("**********************")
//print("Long \(manager.location!.coordinate.longitude)")
//print("Lati \(manager.location!.coordinate.latitude)")
//print("Alt \(manager.location!.altitude)")
//print("Speed \(manager.location!.speed)")
//print("Accu \(manager.location!.horizontalAccuracy)")
//print("**********************")
//print(Double((vendorLatitute as NSString).doubleValue))
//print(Double((vendorLongitute as NSString).doubleValue))
/*
// restaurant
self.restaurantLatitude = (dict["deliveryLat"] as! String)
self.restaurantLongitude = (dict["deliveryLong"] as! String)
// driver
self.driverLatitute = (dict["resturentLatitude"] as! String)
self.driverLongitude = (dict["resturentLongitude"] as! String)
*/
let restaurantLatitudeDouble = Double(self.restaurantLatitude)
let restaurantLongitudeDouble = Double(self.restaurantLongitude)
let driverLatitudeDouble = Double("\(manager.location!.coordinate.latitude)") //Double(self.driverLatitute)
let driverLongitudeDouble = Double("\(manager.location!.coordinate.longitude)") // Double(self.driverLongitude)
let coordinate₀ = CLLocation(latitude: restaurantLatitudeDouble!, longitude: restaurantLongitudeDouble!)
let coordinate₁ = CLLocation(latitude: driverLatitudeDouble!, longitude: driverLongitudeDouble!)
/************************************** RESTAURANT LATITUTDE AND LONGITUDE ********************************/
// first location
let sourceLocation = CLLocationCoordinate2D(latitude: restaurantLatitudeDouble!, longitude: restaurantLongitudeDouble!)
/********************************************************************************************************************/
/************************************* DRIVER LATITUTDE AND LINGITUDE ******************************************/
// second location
let destinationLocation = CLLocationCoordinate2D(latitude: driverLatitudeDouble!, longitude: driverLongitudeDouble!)
/********************************************************************************************************************/
//print(sourceLocation)
//print(destinationLocation)
let sourcePin = customPin(pinTitle: "You", pinSubTitle: "", location: sourceLocation)
let destinationPin = customPin(pinTitle: "Driver", pinSubTitle: "", location: destinationLocation)
/***************** REMOVE PREVIUOS ANNOTATION TO GENERATE NEW ANNOTATION *******************************************/
self.mapView.removeAnnotations(self.mapView.annotations)
/********************************************************************************************************************/
self.mapView.addAnnotation(sourcePin)
self.mapView.addAnnotation(destinationPin)
let sourcePlaceMark = MKPlacemark(coordinate: sourceLocation)
let destinationPlaceMark = MKPlacemark(coordinate: destinationLocation)
let directionRequest = MKDirections.Request()
directionRequest.source = MKMapItem(placemark: sourcePlaceMark)
directionRequest.destination = MKMapItem(placemark: destinationPlaceMark)
directionRequest.transportType = .automobile
let directions = MKDirections(request: directionRequest)
directions.calculate { [self] (response, error) in
guard let directionResonse = response else {
if let error = error {
print("we have error getting directions==\(error.localizedDescription)")
}
return
}
/***************** REMOVE PREVIUOS POLYLINE TO GENERATE NEW POLYLINE *******************************/
let overlays = self.mapView.overlays
self.mapView.removeOverlays(overlays)
/************************************************************************************/
/***************** GET DISTANCE BETWEEN TWO CORDINATES *******************************/
let distanceInMeters = coordinate₀.distance(from: coordinate₁)
// print(distanceInMeters as Any)
// remove decimal
let distanceFloat: Double = (distanceInMeters as Any as! Double)
// print(distanceFloat as Any)
// self.lblDistance.text = (String(format: "Distance : %.0f Miles away", distanceFloat/1609.344))
self.lblTotalDistance.text = (String(format: "Distance : %.0f Miles away", distanceFloat/1609.344))
// print(distanceFloat/1609.344)
// print(String(format: "Distance : %.0f Miles away", distanceFloat/1609.344))
let s:String = String(format: "%.0f",distanceFloat/1609.344)
// print(s as Any)
/************************************************************************************/
/***************** GENERATE NEW POLYLINE *******************************/
let route = directionResonse.routes[0]
self.mapView.addOverlay(route.polyline, level: .aboveRoads)
let rect = route.polyline.boundingMapRect
self.mapView.setRegion(MKCoordinateRegion(rect), animated: true)
/************************************************************************************/
}
self.mapView.delegate = self
print("update location after 5 sec")
// self.locManager.stopUpdatingLocation()
// speed = distance / time
}
// line width of poly line
//MARK:- MapKit delegates -
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let renderer = MKPolylineRenderer(overlay: overlay)
renderer.strokeColor = UIColor.blue
renderer.lineWidth = 4.0
return renderer
}
I am trying to show three things on map :
GPS (the current location) , Marker 1 , Marker 2, but I am not sure I am doing it right or not ! Here is my code :
self.startMarker.position = CLLocationCoordinate2D(latitude: self.startLatitude, longitude: self.startLongitude)
self.startMarker.icon = #imageLiteral(resourceName: "Pin Start")
self.startMarker.map = self.mapView
self.endMarker.position = CLLocationCoordinate2D(latitude: self.endLatitude, longitude: self.endLongitude)
self.endMarker.icon = #imageLiteral(resourceName: "Pin End")
self.endMarker.map = self.mapView
let southWest = CLLocationCoordinate2DMake(self.startLatitude,self.startLongitude)
let northEast = CLLocationCoordinate2DMake(self.endLatitude,self.endLongitude)
let bounds = GMSCoordinateBounds(coordinate: northEast, coordinate: southWest)
let camera = self.mapView.camera(for: bounds, insets:.zero)
self.mapView.camera = camera!
More Code :
// MARK: - Google Map
private func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
if status == CLAuthorizationStatus.authorizedWhenInUse {
mapView.isMyLocationEnabled = true
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let newLocation = locations.last
mapView.camera = GMSCameraPosition.camera(withTarget: newLocation!.coordinate, zoom: 14)
mapView.isMyLocationEnabled = true
}
The result is like this :
What I need :
EDITED
if let myLocation = self.mapView.myLocation {
let path = GMSMutablePath()
path.add(myLocation.coordinate)
path.add(self.startMarker.position)
path.add(self.endMarker.position)
let bounds = GMSCoordinateBounds(path: path)
self.mapView.animate(with: GMSCameraUpdate.fit(bounds, withPadding: 40))
}
Showing All Markers with screen bound:
Here I am trying to provide you one simple example, hope this will help you.
Using this, you can show any number of markers on screen.
Example:
let path = GMSMutablePath()
for var i in 0 ..< array.count //Here take your "array" which contains lat and long.
{
let marker = GMSMarker()
let lat = Double(array.objectAtIndex(i).valueForKey(lattitude) as! String)
let long = Double(arrayr.objectAtIndex(i).valueForKey(longitude) as! String)
marker.position = CLLocationCoordinate2DMake(lat!,long!)
path.addCoordinate(marker.position)
marker.map = self.mapView
}
let bounds = GMSCoordinateBounds(path: path)
self.mapView!.animateWithCameraUpdate(GMSCameraUpdate.fitBounds(bounds, withPadding: 30.0))
Can you try the below code, this is converted from ObjC code here is the documentation of includingCoordinate
let bounds = GMSCoordinateBounds()
bounds.includingCoordinate(self.startMarker.position)
bounds.includingCoordinate(self.endMarker.position)
bounds.includingCoordinate(yourCurrentLocationPosition)
self.mapView.animateWithCameraUpdate(GMSCameraUpdate(fitBounds:bounds, padding:20.0f))
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
showMarkers(locations)
//stop updating location when you find suitable
}
func showMarkers(userLocation: [CLLocation]){
let location1: CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: self.startLatitude, longitude: self.startLongitude)
let location2: CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: self.endLatitude, longitude: self.endLongitude)
let location3: CLLocationCoordinate2D = userLocation[0].coordinate
var locationArray = []
locationArray.append(location1)
locationArray.append(location2)
locationArray.append(location3)
var bounds = GMSCoordinateBounds()
for location in locationArray
{
let latitude = location.valueForKey("latitude")
let longitude = location.valueForKey("longitude")
let marker = GMSMarker()
marker.position = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
marker.map = self.mapView
bounds = bounds.includingCoordinate(marker.position)
}
let update = GMSCameraUpdate.fitBounds(bounds, withPadding: 100)
mapView.animateWithCameraUpdate(update)
}
Note:
locationArray contains three locations ie. Marker 1, Marker 2, user location.
For someone who is looking for solution with Objective-C, This might help
//Locations
CLLocationCoordinate2D source = CLLocationCoordinate2DMake(19.2880, 72.1587);
CLLocationCoordinate2D userLocation = CLLocationCoordinate2DMake(19.1780, 72.9577);
CLLocationCoordinate2D destination = CLLocationCoordinate2DMake(19.0760, 72.8777);
//Create a GMSMutablePath
GMSMutablePath *path = [GMSMutablePath new];
[path addCoordinate:source];
[path addCoordinate:userLocation];
[path addCoordinate:destination];
//Create bounds
GMSCoordinateBounds *bounds = [[GMSCoordinateBounds alloc]initWithPath:path];
//Update the camera position
[mapview animateWithCameraUpdate:[GMSCameraUpdate fitBounds:bounds]];
I'm using the function reverseGeocodeLocation to turn coordinates (which I use for pinpoints) to turn into an address.
I've come up with this code:
func displayMarkers(/*completion: #escaping (CLPlacemark!)->()*/)
{
let annotationView = MKAnnotationView()
var integerCount = 0
let detailButton: UIButton = UIButton(type: .detailDisclosure)
annotationView.rightCalloutAccessoryView = detailButton
let geoCoder = CLGeocoder()
getFromDatabase { (locs) in
// Hier is "locs" de [CLLocationCoordinate2D] array
for location in locs{
let loca = CLLocation(latitude: location.latitude, longitude: location.longitude)
geoCoder.reverseGeocodeLocation(loca){placemarks, error in
var placemark : CLPlacemark!
placemark = placemarks?[0]
//let streetname = (placemark.addressDictionary?["Street"])
//let city = (placemark.addressDictionary?["City"])
//let cityAndStreet = "\(streetname!) \(city!)"
//completion(placemark)
}
//self.displayMarkers { (allPlacemarks) in
//let streetname = (allPlacemarks.addressDictionary?["Street"])
//let city = (allPlacemarks.addressDictionary?["City"])
//let cityAndStreet = "\(streetname!) \(city!)"
let annotation = MKPointAnnotation()
annotation.coordinate = location
annotation.title = "Taxi \(integerCount)"
annotation.subtitle = ""
self.mapView.addAnnotation(annotation)
}
integerCount = integerCount + 1
}
}
My question is, I can't get the completion working (so I commented it out).
When I use this completion like this, I get an error in my viewdidload where I call displayMarkers(), the error says I need to put in an argument which I don't have.
Is there any other way how I can get usable information out of it so I can put the address at annotation.subtitle ? I would be really glad to here it!
Try this,
fix
func displayMarkers(completion: #escaping (CLPlacemark!)->()) {}
to
func displayMarkers(completion: #escaping (_ placemark:CLPlacemark)->()) {}
if it wroks, however, you might better guard the error must to be nil, than you can get the placemarks safely.
Just wondering How to do that , really would like this in my custom cell in the table view in my app...
Will appreciate any help thank you !
You can calculate the distance between two CLLocation objects with the distanceFromLocation method:
let newYork = CLLocation(latitude: 40.725530, longitude: -73.996738)
let sanFrancisco = CLLocation(latitude: 37.768, longitude: -122.441)
let distanceInMeters = newYork.distanceFromLocation(sanFrancisco)
With an MKMapView object and an MKAnnotationView object, you can calculate the distance between the user's current location and the annotation as follows:
if let userLocation = mapView.userLocation.location, annotation = annotationView.annotation {
// Calculate the distance from the user to the annotation
let annotationLocation = CLLocation(latitude: annotation.coordinate.latitude, longitude: annotation.coordinate.longitude)
let distanceFromUserToAnnotationInMeters = userLocation.distanceFromLocation(annotationLocation)
...
}
The following function uses the NSNumberFormatter class to format a distance in meters or kilometres (if the number of meters is more than 1000):
func formatDistance(distanceInMeters: CLLocationDistance) -> String? {
// Set up a number formatter with two decimal places
let numberFormatter = NSNumberFormatter()
numberFormatter.numberStyle = .DecimalStyle
numberFormatter.maximumFractionDigits = 2
// Display as kilometers if the distance is more than 1000 meters
let distanceToFormat: CLLocationDistance = distanceInMeters > 1000 ? distanceInMeters/1000.0 : distanceInMeters
let units = distanceInMeters > 1000 ? "Km" : "m"
// Format the distance
if let formattedDistance = numberFormatter.stringFromNumber(distanceToFormat) {
return "\(formattedDistance)\(units)"
} else {
return nil
}
}
Putting all this together gives us the following:
if let userLocation = mapView.userLocation.location, annotation = annotationView.annotation {
// Calculate the distance from the user to the annotation
let annotationLocation = CLLocation(latitude: annotation.coordinate.latitude, longitude: annotation.coordinate.longitude)
let distanceFromUserToAnnotationInMeters = userLocation.distanceFromLocation(annotationLocation)
if let formattedDistance = formatDistance(distanceFromUserToAnnotationInMeters) {
// Now set the vaue of your label to formattedDistance
}
}
I have created route with multiple annotations.
I want to display text between annotations which exactly as attached screen shot.
Can any one help please?
Thanks
I tried something which will show the distance between two annotation but not when you tap on the MKPolylineOverlay. One more important thing I am not maintaining any standards.
Here is my controller structure.
import UIKit
import MapKit
class RouteViewController: UIViewController, MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
//Rest of the code see below
}
First of all I'll add some annotation to map in the viewDidLoad delegate method as below.
override func viewDidLoad() {
super.viewDidLoad()
self.mapView.delegate = self
let annotation1 = MKPointAnnotation()
annotation1.title = "Times Square"
annotation1.coordinate = CLLocationCoordinate2D(latitude: 40.759011, longitude: -73.984472)
let annotation2 = MKPointAnnotation()
annotation2.title = "Empire State Building"
annotation2.coordinate = CLLocationCoordinate2D(latitude: 40.748441, longitude: -73.985564)
let annotation3 = MKPointAnnotation()
annotation3.title = "Some Point"
annotation3.coordinate = CLLocationCoordinate2D(latitude: 40.7484, longitude: -73.97)
let arrayOfPoints = [ annotation1, annotation2, annotation3]
self.mapView.addAnnotations(arrayOfPoints)
self.mapView.centerCoordinate = annotation2.coordinate
for (index, annotation) in arrayOfPoints.enumerate() {
if index < (arrayOfPoints.count-1) {
//I am taking the two consecutive annotation and performing the routing operation.
self.directionHandlerMethod(annotation.coordinate, ePoint: arrayOfPoints[index+1].coordinate)
}
}
}
In the directionHandlerMethod, I am performing the actual request for direction as below,
func directionHandlerMethod(sPoint: CLLocationCoordinate2D, ePoint: CLLocationCoordinate2D) {
let sourcePlacemark = MKPlacemark(coordinate: sPoint, addressDictionary: nil)
let destinationPlacemark = MKPlacemark(coordinate: ePoint, addressDictionary: nil)
let sourceMapItem = MKMapItem(placemark: sourcePlacemark)
let destinationMapItem = MKMapItem(placemark: destinationPlacemark)
let directionRequest = MKDirectionsRequest()
directionRequest.source = sourceMapItem
directionRequest.destination = destinationMapItem
directionRequest.transportType = .Automobile
let directions = MKDirections(request: directionRequest)
directions.calculateDirectionsWithCompletionHandler {
(response, error) -> Void in
guard let response = response else {
if let error = error {
print("Error: \(error)")
}
return
}
//I am assuming that it will contain one and only one result so I am taking that one passing to addRoute method
self.addRoute(response.routes[0])
}
}
Next I am adding the polyline route on map in the addRoute method as below,
func addRoute(route: MKRoute) {
let polyline = route.polyline
//Here I am taking the centre point on the polyline and placing an annotation by giving the title as 'Route' and the distance in the subtitle
let annoatation = MKPointAnnotation()
annoatation.coordinate = MKCoordinateForMapPoint(polyline.points()[polyline.pointCount/2])
annoatation.title = "Route"
let timeInMinute = route.expectedTravelTime / 60
let distanceString = String.localizedStringWithFormat("%.2f %#", timeInMinute, timeInMinute>1 ? "minutes" : "minute")
annoatation.subtitle = distanceString
self.mapView.addAnnotation(annoatation)
self.mapView.addOverlay(polyline)
}
Next I am implementing the rendererForOverlay delegate method as below,
func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer {
let renderer = MKPolylineRenderer(overlay: overlay)
renderer.strokeColor = UIColor.blueColor()
renderer.lineWidth = 2
renderer.lineCap = .Butt
renderer.lineJoin = .Round
return renderer
}
Next one is the important one delegate method which is viewForAnnotation. Here I am doing some things like placing the label instead of the annotation as below,
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
if annotation.title != nil && annotation.title!! == "Route" {
let label = UILabel()
label.adjustsFontSizeToFitWidth = true
label.backgroundColor = UIColor.whiteColor()
label.minimumScaleFactor = 0.5
label.frame = CGRect(x: 0, y: 0, width: 100, height: 30)
label.text = annotation.subtitle ?? ""
let view = MKAnnotationView()
view.addSubview(label)
return view
}
return nil
}