I'm trying to draw polyline in realtime to show users the route they have taken so far. I use google map api, and so far, it shows users' current location with no problem. But polyline doesn't work though (It doesn't draw polyline at all). I call startMonitoringSignificantLocationChanges after I check for authorization and draw the polyline inside didupdatelocations. Here's an relevant part of my code :
extension MapViewController: CLLocationManagerDelegate {
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
if status == .AuthorizedAlways {
locationManager.startUpdatingLocation()
mapView.myLocationEnabled = true
mapView.settings.myLocationButton = true
locationManager.startMonitoringSignificantLocationChanges()
}
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let location = manager.location {
mapView.camera = GMSCameraPosition(target: location.coordinate, zoom: 15, bearing: 0, viewingAngle: 0)
path.addCoordinate(CLLocationCoordinate2D(latitude: location.coordinate.latitude,
longitude: location.coordinate.longitude))
let polyline = GMSPolyline(path: path)
polyline.strokeColor = UIColor.redColor()
polyline.strokeWidth = 3
polyline.map = mapView
locationManager.stopUpdatingLocation()
}
}
UPDATE-1
After commenting out stopUpdatingLocation line, it does draw the line. But the line is mangled mess.
UPDATE-2
I figured out why the line is a mess instead of one straight line. It's because iphone is changing current location consistently(even though it is stationary), therefore, drawing multiple lines in a small area. How do I stop iphone from doing that?
UPDATE-3
I just found out that "Jumpy" current location does not only happen in my app. It also happens in GoogleMap app. So this is probably Iphone/ios GPS issue.
You just need to ignore invalid location updates or the updates with low accuracy.
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
CLLocation *location = [locations lastObject];
NSTimeInterval age = -[location.timestamp timeIntervalSinceNow];
if (age > 120) return; // ignore old (cached) updates
if (location.horizontalAccuracy < 0) return; // ignore invalid updates
if (location.horizontalAccuracy <=10) //you can change 10 to 20 if you want more frequent updates
{
// this is a valid update
}
}
Hope this helps.
Related
I'm calling direction API to draw the route and trying to move the current location marker icon as user moves the location, But I m facing following problems while implementation.
while moving the icon, It appears like icon got tilt.
It doesn't display the actual user position on the route, Icon appears like it is traveling on the side of route.
My location icon (blue icon) on google map continuously changes its position.
func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
let direction = newHeading.trueHeading
lastDriverAngleFromNorth = direction
self.sourceMarker?.rotation = (lastDriverAngleFromNorth - mapBearing) - bearingValue
DispatchQueue.main.async {
CATransaction.begin()
CATransaction.setValue(2, forKey: kCATransactionAnimationDuration)
self.gmsmapView?.animate(toBearing: newHeading.magneticHeading)
CATransaction.commit()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let lastLocation = locations.last {
self.currentLocation = lastLocation
let zoom = self.gmsmapView?.camera.zoom ?? 20.0
zoomLevel = zoom
let destination = CLLocation.init(latitude: viewModel.marker.location[0], longitude: viewModel.marker.location[1])
let bearing = getBearingBetweenTwoPoints(point1: lastLocation.coordinate, point2: destination.coordinate)
self.cameraMoveToLocation(toLocation: lastLocation, zoom:zoom, bearing: bearing)
}
}
func mapView(_ mapView: GMSMapView, didChange position: GMSCameraPosition) {
mapBearing = position.bearing
if let coordinate = self.currentLocation?.coordinate{
self.rerouteCalculation(currentLocation:coordinate)
}
self.sourceMarker?.rotation = (lastDriverAngleFromNorth - mapBearing) - bearingValue
}
func cameraMoveToLocation(toLocation: CLLocation, zoom : Float, bearing : Double) {
self.gmsmapView?.animate(toLocation: toLocation.coordinate)
self.sourceMarker?.position = toLocation.coordinate
}
Can anyone please help me, I'm stuck here.
Make sure that you choose the correct location accuracy for your needs.
CCLocationAccuracy offers the following options:
kCLLocationAccuracyBestForNavigation: <= You should use this when the device is plugged to a power source
The highest possible accuracy that uses additional sensor data to
facilitate navigation apps.
kCLLocationAccuracyBest: <= You should use this when the device runs on battery
The best level of accuracy available.
kCLLocationAccuracyNearestTenMeters: Accurate
to within ten meters of the desired target.
kCLLocationAccuracyHundredMeters: Accurate to
within one hundred meters.
kCLLocationAccuracyKilometer: Accurate to the
nearest kilometer.
kCLLocationAccuracyThreeKilometers: Accurate
to the nearest three kilometers.
I am working on construction project and for that I want to fetch exact current location which must satisfy the accuracy withing the 1 meter. I am using google maps with SDK "CLLocationManager" and I am getting the current location but the location is not exact, it has some (+/-)5 meters to (+/-)10 meters error in location. I want the exact/accurate current location which should not exceeds the location accuracy error more than a feet.
Please help me out to fetch EXACT CURRENT LOCATION.
Also, is there any third party library, any hardware device (which I can connect to iOS device.) or anything else, please let me know.
Your valuable comment will be most appreciate.
Edited:-
Here I am sharing my code to get the current location using CLLocationManager:
override func viewDidLoad()
{
super.viewDidLoad()
self.locationManager = CLLocationManager()
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.requestAlwaysAuthorization()
self.locationManager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
{
let position = CLLocationCoordinate2D(latitude: manager.location!.coordinate.latitude, longitude: manager.location!.coordinate.longitude)
marker.position = position
print("position:",position)
}
Thank you..
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]){
guard let location = manager.location else{
return
}
var currentLocationCoordinate = location.coordinate
}
using this delegate function you will get the current location
You can use external gps if you want to get the exact location, with the help of device you will always get this fluctuation. You can also set your location accuracy to best.
https://developer.apple.com/library/content/documentation/Performance/Conceptual/EnergyGuide-iOS/LocationBestPractices.html
You should always relay on GPS for accurate location. You can set locationManager.desireAccuracy = kCLLocationAccuracyBest. It will call you locationManager didUpdateLocation with the location array with each location having its accuracy you can apply your logic here.
Be alert to call locationManager.stopUpdatingLocation() when you done with desire accuracy location.
e.g
let horizontalAccuracy: Double = 20.0
let howRecent = location.timestamp.timeIntervalSinceNow
guard CLLocationCoordinate2DIsValid(location.coordinate),
location.horizontalAccuracy > 0,
location.horizontalAccuracy < horizontalAccuracy,
abs(howRecent) < 10 else { return false }
return true
}
Using the following code, my currentLat and currentLong are not updating beyond the first location. locations never has more than 1 item, and it's always exactly the same.
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let loc: CLLocation = locations[locations.count - 1]
currentLat = loc.coordinate.latitude
currentLong = loc.coordinate.longitude
}
All the searching I've been doing has only shown how to get it to work if it doesn't work at all, but this DOES work, but only once.
The way I expect this to work is to continuously update the GPS coordinates and fire this delegate off whenever the location is updated. Does the location keep updating the entire time? I thought it would due to the terminology of "startUpdatingLocation()" and there being a "stopUpdatingLocation()." Am I trying to use this incorrectly?
Like I said, it works for the first time, so it's loaded and started just fine, the permissions are allowed, the info.plist has the necessary entries.
When you want to update your location, you should stop existing update once it got new location. You can use stopUpdatingLocation() in your didUpdateLocations: method to do so.
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// This should match your CLLocationManager()
locManager.stopUpdatingLocation()
let loc: CLLocation = locations[locations.count - 1]
currentLat = loc.coordinate.latitude
currentLong = loc.coordinate.longitude
}
Then call your locManager.startUpdatingLocation() when you need new location.
I have been doing some iOS development for a couple of months and recently I am developing a bus app.
I am currently mimicking the bus' movements and set up multiple annotations on the bus stops. For test purposes, I have setup just one bus stop and am trying to monitor when the bus has entered this region and exited as well.
Strangely, my didStartMonitoringForRegion method is called perfectly but neither the didEnterRegion nor didExitRegion methods are called. Every time I run the program, the bus pretty much passes the stop without prompting me so.
Could someone explain to me why this is happening and how to resolve it?
let locationManager = CLLocationManager()
var allBusAnnotations = [MKPointAnnotation]()
var summitEastBusStations = [CLLocationCoordinate2D]()
var busStopNames = ["Dix Stadium", "Risman Plaza", "Terrace Drive", "Terrace Drive 2","C-Midway","Theatre Dr.","East Main Street","South Lincoln"]
var radius = 500 as CLLocationDistance
// 0.02 is the best zoom in factor
var mapZoomInFactor : Double = 0.02
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.getBusStop()
self.locationManager.delegate = self
// gets the exact location of the user
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
// gets the user's location only when the app is in use and not background
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
self.mapView.showsUserLocation = true
self.setBusStopAnnotations(summitEastBusStations)
// self.mapView.mapType = MKMapType.Satellite
}
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
// sends the latitude and longitude to the Apple Servers then returns the address
CLGeocoder().reverseGeocodeLocation(manager.location, completionHandler: { (placeMarks: [AnyObject]!, error: NSError!) -> Void in
if error != nil
{
println("Reverse Geocode Failed: " + error.localizedDescription)
return
}
if placeMarks.count > 0
{
// gets the most updated location
let pm = placeMarks.last as! CLPlacemark
let centre = CLLocationCoordinate2D(latitude: manager.location.coordinate.latitude, longitude: manager.location.coordinate.longitude)
// draws a circle in which the map will zoom to
let region = MKCoordinateRegion(center: centre, span: MKCoordinateSpan(latitudeDelta: self.mapZoomInFactor, longitudeDelta: self.mapZoomInFactor))
self.mapView.setRegion(region, animated: true)
self.displayLocationInfo(pm)
// self.distanceToClosestAnnotation(pm)
self.geoFencing()
// YOU CAN IGNORE THIS WHOLE PART. IT'S IRRELEVANT FOR THIS QUESTION
var repeatTimes = 0
var count = 0
while(count <= 7)
{
if count == (self.summitEastBusStations.count - 1)
{
count = 1
++repeatTimes
}
else if repeatTimes == 1
{
count = 0
++repeatTimes
}
else if repeatTimes == 2
{
break
}
self.distanceToBusStop(pm, count: count)
++count
}
}
})
}
func locationManager(manager: CLLocationManager!, didFailWithError error: NSError!) {
println("Location Manager Failed: " + error.localizedDescription)
}
func locationManager(manager: CLLocationManager!, didStartMonitoringForRegion region: CLRegion!) {
println("The region is monitored")
println("The monitored region is \(region.description)")
}
func locationManager(manager: CLLocationManager!, monitoringDidFailForRegion region: CLRegion!, withError error: NSError!) {
println("Failed to monitor the stated region")
}
func locationManager(manager: CLLocationManager!, didEnterRegion region: CLRegion!) {
println("The bus has entered the region")
}
func locationManager(manager: CLLocationManager!, didExitRegion region: CLRegion!) {
println("The bus has left the region")
}
func geoFencing()
{
let rismanPlaza = CLLocationCoordinate2D(latitude: 41.1469492, longitude: -81.344068)
var currentBusStop = CLLocation(latitude: rismanPlaza.latitude, longitude: rismanPlaza.longitude)
addRadiusCircle(currentBusStop)
let busStopRegion = CLCircularRegion(center: CLLocationCoordinate2D(latitude: rismanPlaza.latitude, longitude: rismanPlaza.longitude), radius: radius, identifier: busStopNames[1])
if radius > self.locationManager.maximumRegionMonitoringDistance
{
radius = self.locationManager.maximumRegionMonitoringDistance
}
locationManager.startMonitoringForRegion(busStopRegion)
}
// creates the radius around the specified location
func addRadiusCircle(location: CLLocation)
{
self.mapView.delegate = self
var circle = MKCircle(centerCoordinate: location.coordinate, radius: radius)
self.mapView.addOverlay(circle)
}
// performs the actual circle colouring
func mapView(mapView: MKMapView!, rendererForOverlay overlay: MKOverlay!) -> MKOverlayRenderer!
{
if overlay is MKCircle
{
var circle = MKCircleRenderer(overlay: overlay)
circle.strokeColor = UIColor.redColor()
circle.fillColor = UIColor(red: 255, green: 0, blue: 0, alpha: 0.1)
circle.lineWidth = 1
return circle
}
else
{
return nil
}
}
I ended up using the CLRegion.containsCoordinate(location.coordinate) method instead. It works pretty much the same way.
Once the object has entered my set region, it returns true and from here I can know when it's entered and exited the region.
Please ensure [CLLocationManager regionMonitoringAvailable] returns YES and
CLLocationManager.monitoredRegions contains valid regions.
Also, from Apple documentation:
In iOS 6, regions with a radius between 1 and 400 meters work better
on iPhone 4S or later devices. (In iOS 5, regions with a radius
between 1 and 150 meters work better on iPhone 4S and later devices.)
On these devices, an app can expect to receive the appropriate region
entered or region exited notification within 3 to 5 minutes on
average, if not sooner.
And
Apps can expect a notification as soon as the device moves 500 meters
or more from its previous notification. It should not expect
notifications more frequently than once every five minutes. If the
device is able to retrieve data from the network, the location manager
is much more likely to deliver notifications in a timely manner.
There are many causes why your delegates are not triggering. First goto target settings and in capabilities tab check whether in BackgroundModes Location Updates is enabled.
If it is on then try to check whether your current location manager holds the region you've specified by checking
NSLog(#"Monitored Regions %#",self.locationManager.monitoredRegions);
Then if the user device is at current location(Latitude and longitude) the didEnterRegion: and didExitRegion: delegates will not be triggering. Use didDetermineState: method to find whether the user/device is in current region that is moniotred. If it so, didDetermineState: will be triggered. Once the user leaves the region didExitRegion: will be triggered.
Then after also if delegates aren't trigerring then find then error using the following in delegate
- (void)locationManager:(CLLocationManager *)manager monitoringDidFailForRegion:(CLRegion *)region withError:(NSError *)error {
NSLog(#"Failed to Monitor %#", error);
}
I am adding a mapKit for my app, and I setting the middle of the map to be the persons current location, in lat and lng. I am able to get it working, however, it keeps updating every so often. I only want it to update once when the app is loaded, then not update anymore. Maybe update every 2 minutes. Here is my code:
func locationManager(manager: CLLocationManager!,
didUpdateLocations locations: [AnyObject]!)
{
var latestLocation = locations.last as! CLLocation
let location = CLLocationCoordinate2D(latitude: latestLocation.coordinate.latitude, longitude: latestLocation.coordinate.longitude)
let span = MKCoordinateSpanMake(0.015, 0.015)
//Let our point be the center of our span
//region holds value of span and location
let region = MKCoordinateRegion(center: location, span: span)
//set up region on mapView
mapView.setRegion(region, animated: true)
//MKPointAnnotation defines a concrete annotation
let annotation = MKPointAnnotation()
}
If you are targeting only iOS 9, look at the new requestLocation() method. It addresses your problem. If you need to target older versions of iOS, you could add stopMonitoringLocation() to the
func locationManager(
manager: CLLocationManager!,
didUpdateLocations locations: [AnyObject]!
)
This will stop the monitoring once you get one reading...
I'm not sure what your starting you location manager with, but I think you might want to look at using:
startMonitoringSignificantLocationChanges()
to start your location manager. Here is apple's documentation on the subject: https://developer.apple.com/library/prerelease/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/index.html#//apple_ref/occ/instm/CLLocationManager/startMonitoringSignificantLocationChanges