geolocationapplescriptlocation-services

How can I tell my devices current distance from a location using AppleScript?


Building on this solution: AppleScript - Geographic Location, I'm trying to use CLLocation's distance method to create a simple geo-fencing script (setting my status based on distance from certain locations).

I'm not an AppleScript pro, so I'm struggling with the concept of how to initialise a CLLocation object in the script (with latitude and longitude) to compare to my current location returned by the original answer's script.

So I'm looking for something like this, where myCurrentLocation is the location object returned from the original script (obviously wrong, just some pseudo obj-c stuff):

set tagetLatitude to "50.850206"
set targetLongitude to "5.690947"
set targetLocation to CLLocation's initWithLatitude:tagetLatitude longitude:targetLongitude

set distance to targetLocation's distance:myCurrentLocation
return distance

This is the original script by CJK for reference:

use framework "CoreLocation"
use framework "Foundation"
use scripting additions

property this : a reference to the current application
property nil : a reference to missing value
property _1 : a reference to reference

property CLLocationManager : a reference to CLLocationManager of this
property kCLLocationAccuracyThreeKilometers : a reference to 3000.0
--------------------------------------------------------------------------------
property running : false
property result : missing value -- Lat./long. or error description
property number : 0 -- Error code
property seconds : 10 -- Maximum time to allow script to run
--------------------------------------------------------------------------------
# IMPLEMENTATION:
my performSelectorOnMainThread:"getLocation" withObject:nil waitUntilDone:true
return my result
--------------------------------------------------------------------------------
# HANDLERS & SCRIPT OBJECTS:
to getLocation()
    set locationManager to CLLocationManager's new()

    locationManager's setDelegate:me
    locationManager's setDesiredAccuracy:kCLLocationAccuracyThreeKilometers

    set my running to true
    set started to current date

    locationManager's startUpdatingLocation()

    repeat while my running
        delay 0.5
        if (current date) - started > my seconds then exit repeat
    end repeat
end getLocation

on locationManager:locationManager didUpdateLocations:locations
    local locationManager, locations

    locationManager's stopUpdatingLocation()

    set my running to false
    set my result to (locations's valueForKey:"coordinate") as record
end locationManager:didUpdateLocations:

on locationManager:locationManager didFailWithError:err
    local locationManager, err

    tell err's code()
        set my number to it
        set my result to item (it + 1) in my enum's kCLError
        if it ≠ 0 then set my running to false
    end tell
end locationManager:didFailWithError:

script enum
    property kCLError : {¬
        "Location Unknown", ¬
        "Denied", ¬
        "Network", ¬
        "Heading Failure", ¬
        "Region Monitoring Denied", ¬
        "Region Monitoring Failure", ¬
        "Region Monitoring Setup Delayed", ¬
        "Region Monitoring Response Delayed", ¬
        "Geocode Found No Result", ¬
        "Geocode Found Partial Result", ¬
        "Geocode Canceled", ¬
        "Deferred Failed", ¬
        "Deferred Not Updating Location", ¬
        "Deferred Accuracy Too Low", ¬
        "Deferred Distance Filtered", ¬
        "Deferred Canceled", ¬
        "Ranging Unavailable", ¬
        "Ranging Failure"}
    property CLAuthorizationStatus : {¬
        "Not Determined", ¬
        "Restricted", ¬
        "Denied", ¬
        "Authorized (Always)", ¬
        "Authorized When In Use"}
end script
---------------------------------------------------------------------------❮END❯

Solution

  • You aren't far off actually:

    on distance to {latitude:|𝝓|, longitude:|λ|}
            ((my location)'s distanceFromLocation:(CLLocation's ¬
                    alloc()'s initWithLatitude:|𝝓| longitude:|λ|)) ¬
                    as real
    end distance
    

    where my location is an instance of CLLocation. Therefore, in the original script, instead of:

    set my result to (locations's valueForKey:"coordinate")
    

    you want:

    set my result to item 1 in locations
    

    since locations ought to be an NSArray of CLLocation values.

    However, since writing that script, there's been a new instance method added to the CLLocationManager class, namely requestLocation()

    So, the script can probably be shortened a little to something like this:

    --------------------------------------------------------------------------------
    use framework "Foundation"
    use framework "CoreLocation"
    --------------------------------------------------------------------------------
    property this : a reference to current application
    property nil : a reference to missing value
    
    property CLLocation : a reference to CLLocation of this
    property CLLocationManager : a reference to CLLocationManager of this
    --------------------------------------------------------------------------------
    property location : []
    --------------------------------------------------------------------------------
    # IMPLEMENTATION:
    my performSelectorOnMainThread:"findMe" withObject:nil waitUntilDone:yes
    return the distance to {latitude:-90.0, longitude:0.0}
    
    to findMe()
            tell CLLocationManager's new()
                    setDelegate_(me)
                    setDesiredAccuracy_(3000.0)
                    requestLocation()
            end tell
    
            repeat until my location ≠ []
                    delay 1
            end repeat
    end findMe
    --------------------------------------------------------------------------------
    # MAIN HANDLERS:
    on distance from A as {record, reference} : [] to B as {record, reference}
            local A, B
    
            if A = [] then set A to my location
            if A's class = record then
                    set {latitude:|𝝓|, longitude:|λ|} to A
                    tell CLLocation's alloc() to set A to ¬
                            initWithLatitude_longitude_(|𝝓|, |λ|)
            end if
            if B's class = record then
                    set {latitude:|𝝓|, longitude:|λ|} to B
                    tell CLLocation's alloc() to set B to ¬
                            initWithLatitude_longitude_(|𝝓|, |λ|)
            end if
    
            (B's distanceFromLocation:A) as meters as kilometers
    end distance
    --------------------------------------------------------------------------------
    # DELEGATED HANDLERS:
    on locationManager:[] didUpdateLocations:here
            set [my location] to here
    end locationManager:didUpdateLocations:
    
    on locationManager:[] didFailWithError:E
            error E's localizedDescription() as text
    end locationManager:didFailWithError:
    ---------------------------------------------------------------------------❮END❯
    

    I haven't tested this script yet as I'm in High Sierra, and it requires Mojave or later. I will reboot into Catalina at some point and check it for errors.