How to know if array count is incrementing in didUpdateLocations - ios

I am new to Swift. I am using Google Maps Sdk's method didUpdateLocations to draw a path on the map.
I am working on a section regarding the array count. I want to run some functions if the array count is increasing. I am storing lat and long in two arrays.
var latarray = [Double]()
var longarray = [Double]()
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
locationManager.startMonitoringSignificantLocationChanges()
locationManager.startUpdatingLocation()
myMapView.clear()
if (self.latarray.count != 0 ) {
longarray.append(long)
latarray.append(lat)
print ("lat array is \(latarray)count is \(latarray.count)")
print ("long array is \(longarray)count is \(longarray.count)")
}
else {
Print("array not increasing ")
}
let location = locations.last
self.lat = (location?.coordinate.latitude)!
self.long = (location?.coordinate.longitude)!
let currtlocation = CLLocation(latitude: lat, longitude: long)
}
Is there any operator which can show if the array content if array count is increasing?

Swift has something called property observers that you can use to execute code when a property is set/changed. They are willSet and didSet and they work fine for arrays as well. You can read more about properties and property observers here
An example
struct Test {
var array = [Int]() {
didSet {
print("Array size is \(array.count)")
}
}
}
var test = Test()
test.array.append(1)
test.array.append(1)
test.array.append(1)
test.array = []
prints
Array size is 1
Array size is 2
Array size is 3
Array size is 0

Related

Calculating trip distance core location swift

I have an application where I calculate distance travelled like the Uber application. When a driver starts a trip, the location begins to change even though a start point has been specified in the search for a ride, a driver could decide to pass an alternative route or pass long places and routes because he/ she does not know the shortest route, how then do I calculate the total distance.
The starting location is the location the driver hits start button
The end location is the location the driver hits stop button
this is my code so far
public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
lastLocation = locations.last!
endTrip(locations.last)
if !hasSetInitialLocation {
let camera = GMSCameraPosition.camera(withTarget: lastLocation!.coordinate, zoom: 17)
self.mapView.animate(to: camera)
hasSetInitialLocation = true
endTrip(lastLocation)
MqttManager.instance.connectToServer()
}
}
func endTrip(endLoaction: CLLocation) {
guard let statusChange = source.getStatusChange() else{return}
var distanceTraveled: Double = 0.0
let initialLocation = CLLocation(latitude: (statusChange.meta?.location?.lat)!, longitude: (statusChange.meta?.location?.lng)!)
let distance = initialLocation.distance(from: endLoaction)
distanceTraveled += distance
let distanceInKM = Utility.convertCLLocationDistanceToKiloMeters(targetDistance: distanceTraveled)
}
How can i calculate the distance to reflect the total distance moved by the driver since there could be a change in route from the proposed start point and end point.
The driver hits a button called start trip, I want to get the distance from that moment till the moment he hits the button end trip
this implementation could be got from a similar working code like these but the only difference is that their is a start button which passes the coordinates at that point and a stop coordinate which is the end of the coordinate.
enum DistanceValue: Int {
case meters, miles
}
func calculateDistanceBetweenLocations(_ firstLocation: CLLocation, secondLocation: CLLocation, valueType: DistanceValue) -> Double {
var distance = 0.0
let meters = firstLocation.distance(from: secondLocation)
distance += meters
switch valueType {
case .meters:
return distance
case .miles:
let miles = distance
return miles
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if startLocation == nil {
startLocation = locations.first
} else if let location = locations.last {
runDistance += lastLocation.distance(from: location)
let calc = calculateDistanceBetweenLocations(lastLocation, secondLocation: location, valueType: .meters)
print("TOTAL LOC 1 \(calc)")
print("TOTAL LOC 2 \(runDistance)")
}
lastLocation = locations.last
}
as shown in my print statements print("TOTAL LOC 1 \(calc)")
print("TOTAL LOC 2 \(runDistance)") how can I make
calc the same with runDistance
here is what is printed in the console
TOTAL LOC 10.29331530774379
TOTAL LOC 2 10.29331530774379
TOTAL LOC 2.2655118031831587
TOTAL LOC 2 12.558827110926948
If you get the distance like this using the first and last coordinate it always returns the wrong value because it can't identify the actual traveling path.
I did resolve the same issue with using the following code.
use GoogleMaps
> pod 'GoogleMaps'
Make the coordinates array while the driver is moving on a route.
var arr = [Any]()
// Driving lat long co-ordinateds continues add in this array according to your expectation either update location or perticuler time duration.
// make GMSMutablePath of your co-ordinates
let path = GMSMutablePath()
for obj in arr{
print(obj)
if let lat = (obj as? NSDictionary)?.value(forKey: PARAMETERS.LET) as? String{
path.addLatitude(Double(lat)!, longitude: Double(((obj as? NSDictionary)?.value(forKey: PARAMETERS.LONG) as? String)!)!)
}
}
print(path) // Here is your traveling path
let km = GMSGeometryLength(path)
print(km) // your total traveling distance.
I did it in this app and it's working fine.
Hope it will helps you :)
OR without GoogleMaps
You have to come with locations, an array of CLLocationCoordinate2D, for yourself, as per your code, though.
class ViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
// MARK: - Variables
let locationManager = CLLocationManager()
// MARK: - IBOutlet
#IBOutlet weak var mapView: MKMapView!
// MARK: - IBAction
#IBAction func distanceTapped(_ sender: UIBarButtonItem) {
let locations: [CLLocationCoordinate2D] = [...]
var total: Double = 0.0
for i in 0..<locations.count - 1 {
let start = locations[i]
let end = locations[i + 1]
let distance = getDistance(from: start, to: end)
total += distance
}
print(total)
}
func getDistance(from: CLLocationCoordinate2D, to: CLLocationCoordinate2D) -> CLLocationDistance {
// By Aviel Gross
// https://stackoverflow.com/questions/11077425/finding-distance-between-cllocationcoordinate2d-points
let from = CLLocation(latitude: from.latitude, longitude: from.longitude)
let to = CLLocation(latitude: to.latitude, longitude: to.longitude)
return from.distance(from: to)
}
}
Output
A simple function to calculate distance (in meters) given an array of CLLocationCoordinate2D. Uses reduce instead of array iteration.
func computeDistance(from points: [CLLocationCoordinate2D]) -> Double {
guard let first = points.first else { return 0.0 }
var prevPoint = first
return points.reduce(0.0) { (count, point) -> Double in
let newCount = count + CLLocation(latitude: prevPoint.latitude, longitude: prevPoint.longitude).distance(
from: CLLocation(latitude: point.latitude, longitude: point.longitude))
prevPoint = point
return newCount
}
}
I like to use an extension for that
extension Array where Element: CLLocation {
var distance: Double {
guard count > 1 else { return 0 }
var previous = self[0]
return reduce(0) { (result, location) -> Double in
let distance = location.distance(from: previous)
previous = location
return result + distance
}
}
}
Usage:
locations.distance

Cannot get updated coordinates to append them the array in SWIFT 4

I'm trying to store into actualRouteInUseAnnotation: [MKAnnotation] start and end position for a route, and into actualRouteInUseCoordinates : [CLLocationCoordinate2D] all the coordinates for the route.
The annotation array gets it's values, but the coordinates array doesn't.
The functions used are:
For start:
func startTracking2() {
self.trackingIsActive = true
locationManager.stopUpdatingLocation()
let startRoutePosition = RouteAnnotation(title: "Route Start", coordinate: (locationManager.location?.coordinate)!, imageName: "Route Start")
self.actualRouteInUseAnnotations.append(startRoutePosition)
// self.actualRouteInUseCoordinates.append((locationManager.location?.coordinate)!)
print(actualRouteInUseCoordinates)
print(actualRouteInUseAnnotations)
}
For stop:
func stopTracking2() {
self.trackingIsActive = false
locationManager.stopUpdatingLocation()
let stopRoutePosition = RouteAnnotation(title: "Route Stop", coordinate: (locationManager.location?.coordinate)!, imageName: "Route Stop")
self.actualRouteInUseAnnotations.append(stopRoutePosition)
print(actualRouteInUseCoordinates)
print(actualRouteInUseAnnotations)
}
and for updated coordinates:
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let mostRecentLocation = locations.last else { return }
// setCoordinates() // set actual user coordinates
// storeUserLocation() // store auctual user coordinates
self.actualRouteInUseCoordinates.append(mostRecentLocation.coordinate)
}
Can you see why I'm not appending aupdated coordinates to the array?

Distance filter not working as expected in standard location services

I am using standard location services to track location.
locationManager = CLLocationManager()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
locationManager.distanceFilter = DISTANCE_LIMIT //value set to 20
locationManager.allowsBackgroundLocationUpdates = true
locationManager.startUpdatingLocation()
With the filter distance set, I assumed that the location updates would be received only when the user location changes by the specified distance. However, the updates were received for movements that were less than the preset distance value.
So I set up my delegate method as:
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard locations.count > 0 else {
return
}
guard let location = locations.last else {
return
}
let previousLocationEncoded = UserDefaults.standard.object(forKey: UserDefaultKey.previousVisitedLocation) as? Data
if previousLocationEncoded == nil {
let encodedLocation = NSKeyedArchiver.archivedData(withRootObject: location)
UserDefaults.standard.set(encodedLocation, forKey: UserDefaultKey.previousVisitedLocation)
// do some task
} else {
let previousLocationDecoded = NSKeyedUnarchiver.unarchiveObject(with: previousLocationEncoded!) as! CLLocation
let distanceBetweenVisits = previousLocationDecoded.distance(from: location)
if distanceBetweenVisits > DISTANCE_LIMIT {
// do some other task
let encodedLocation = NSKeyedArchiver.archivedData(withRootObject: location)
UserDefaults.standard.set(encodedLocation, forKey: UserDefaultKey.previousVisitedLocation)
}
}
}
The objective is to calculate the distance of current location from previously saved location to determine if the location has changed by the distance filter value.
However, sometimes, even if I am not moving, the condition distanceBetweenVisits > DISTANCE_LIMIT becomes true. Is this something dependent on iOS and the GPS? Can such conditions be properly handled?

How to get single dataBase reference from Firebase

I'm sharing and retrieving coordinates with Firebase, but when I print them in my console..I get same coordinates 3-4 time.
Which creates an odd effect on my custom marker image file.
How can I get the coordinates from Firebase only once?
Here is my code:
var posts=[postStruct]()
var mapView : GMSMapView? = nil
var friendLocator : [Locator] = [Locator]()
struct Locator {
let name: String
let long: CLLocationDegrees
let lat: CLLocationDegrees
}
var latPass: Double!
var longPass: Double!
var fetchLat: Double!
var fetchLong: Double!
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
{
var location=locations[0]
let span:MKCoordinateSpan=MKCoordinateSpanMake(0.01, 0.01)
var myLocation:CLLocationCoordinate2D=CLLocationCoordinate2DMake(location.coordinate.latitude, location.coordinate.longitude)
let region:MKCoordinateRegion=MKCoordinateRegionMake(myLocation, span)
latPass=28.3217378
longPass=75.6895935
post()
self.configureMapView()
let dataBaseRef=FIRDatabase.database().reference()
dataBaseRef.child("Raunak Trikha").queryOrderedByKey().observeSingleEvent(of: .childAdded, with: {(snapshot) in
let postDict = snapshot.value as? [String : AnyObject] ?? [:]
var fetchLat = postDict["lat"] as! Double
var fetchLong = postDict["long"] as! Double
let locator = Locator(name: "Raunak Trikha", long: fetchLong, lat: fetchLat)
self.friendLocator.append(locator)
self.locateFriend()
print(fetchLat)
print(fetchLong)
})
manager.stopUpdatingLocation()
self.view = mapView
}
func locateFriend() {
for friend in friendLocator{
let friendMarker = GMSMarker()
friendMarker.position=CLLocationCoordinate2D(latitude: friend.lat, longitude: friend.long)
friendMarker.title=friend.name
friendMarker.map=mapView
mapView?.selectedMarker=friendMarker
if friend.name=="Virat Singh"{
friendMarker.icon=UIImage(named: "ViratPin.png")
}
else if friend.name=="Raunak Trikha"{
friendMarker.icon=UIImage(named: "currentLocation.png")
}
}
do {
mapView?.mapStyle = try GMSMapStyle(jsonString: kMapStyle)
} catch {
NSLog("One or more of the map styles failed to load. \(error)")
}
}
func configureMapView(){
let camera = GMSCameraPosition.camera(withLatitude: latPass, longitude: longPass, zoom: 10)
self.mapView = GMSMapView.map(withFrame: CGRect.zero, camera: camera)
view = mapView
mapView?.settings.scrollGestures = true
mapView?.settings.zoomGestures = true
mapView?.settings.myLocationButton = true
//mapView?.addSubview(searchBar)
//mapView?.addSubview(searchSupporter)
//mapView?.bringSubview(toFront: searchBar)
for gesture in (mapView?.gestureRecognizers!)! {
mapView?.removeGestureRecognizer(gesture)
}
}
when I print fetchLat & fetchLong I get the same coordinates 4 time, which overlaps my custom marker image that creates the weird effect.
Since your code that adds a particular Locator struct is called multiple times, check your array to make sure it doesn't already contain the exact same struct before adding it to the array locally.
This will evaluate your array of structs and determine if there is no value for it. But it also assumes that name property of the struct is a unique identifier for each struct, which may not be your case. You can alternatively compare any value within the filter closure that you want to make sure isn't duplictated, i. e. lat and long.
let locator = Locator(name: "Raunak Trikha", long: fetchLong, lat: fetchLat)
if self.friendLocator.filter({ $0.name == locator.name }).count == 0 {
self.friendLocator.append(locator)
}
self.locateFriend()
This function func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) will get called whenever your location changes/updates or until the GPS settles (Warms up) on your location.
I notice you are using the firebase single event obeserver function for database updates using .observeSingleEvent() which is correct however since you have defined the call in the above didUpdateLocations function it will be called multiple times.
Either move the call to Firebase out of the function or supply some conditional to call firebase only once. I.e only update if the location has changed more than X range/distance etc.

How come my array of CLLocation instances is empty?

I'm currently trying to construct a way that calculates the the distance in feet between my current location and other hard coded locations. I will eventually display these distances as strings in a table view. These are the steps that I'm taking:
1) I have a hard coded library of dictionaries that each hold a "latitude" and "longitude" key-value pair.
2) to extract this information from a struct as follows:
struct BarDetials {
// other properties
....
var lat: CLLocationDegrees?
var long CLLocationDegrees?
init(index: Int) {
// initialize other properties
// ...
lat = specificBarDetail["latitude"] as! CLLocationDegrees!
long = specificBarDetail["longitude"] as! CLLocationDegrees!
}
}
3) I use another struct to create an array of CLLocation instances from these coordinates as follows:
struct ConstructLocationsToCompare {
var latitude : [CLLocationDegrees?] = []
var longitude : [CLLocationDegrees?] = []
var barLocations : [CLLocation?] = []
init(){
for index in 0...21 {
var data = BarDetails(index: index)
if data.lat != nil {
latitude.append(data.lat!)
}
if data.long != nil {
longitude.append(data.long!)
}
barLocations[index] = CLLocation(latitude: latitude[index]!, longitude: longitude[index]!)
}
}
}
4) I then calculate the distances in my MasterViewController().
var latestLocation : AnyObject? , var currentLocation : CLLocation!, and var distanceStringArray : [String?] = [] are all properties of my MasterViewController() class.
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
CLGeocoder().reverseGeocodeLocation(manager.location, completionHandler: { (placemarks, error) -> Void in
// Get latest location and store it as a property of MasterViewController
if self.latestLocation != nil {
self.latestLocation = locations[locations.count - 1]
// Print out the latest locaiton for debuging
println("Latest Location")
println("---------------")
println(self.latestLocation)
// Create an instance of ContructLocationsToCompare to get all locaiton data
var getBarLocations = ConstructLocationsToCompare()
// Get loop to calculate the distance you are away from each Bar
var distanceDoubleArray : [Double?] = []
for index in 0...21 {
var distanceBetween : CLLocationDistance = self.latestLocation!.distanceFromLocation(getBarLocations.barLocations[index])
var distanceInFeet = distanceBetween * 3.28084
distanceDoubleArray[index] = distanceInFeet
self.distancesStringArray.append( String(format: "%6.0 ft", distanceDoubleArray[index]!))
}
}
//println(distanceBetween) or get error if it exists
if error != nil {
println("Error: " + error.localizedDescription)
return
}
if placemarks.count > 0 {
let pm = placemarks[0] as! CLPlacemark
self.displayLocationInfo(pm)
}
})
}
5) Lastly, to display my distance from each location (in my -cellForRowAtIndexPath):
println("")
println("")
println("List of Locations")
println("=================")
println(self.distancesStringArray)
println("")
println("")
println("")
println("")
if self.distancesStringArray[indexPath.row]!.isEmpty == true{
futureCell.distanceAway?.text = "-" // I crash at this line ^^^^^^
} else {
futureCell.distanceAway?.text = self.distancesStringArray[indexPath.row]
}
*** My distanceStringArray is always empty therefore I get a fatal error and the app crashes. How do I fix this? Is the fact that I declare var latestLocation : AnyObject? , var currentLocation : CLLocation!, and var distanceStringArray : [String?] = [] properties of my MasterViewController() class bad practice? If not do these properties need to be casted as a different type/ declared in a different manner?

Resources