The additional functionality provided by Shane Stanley’s companion script inspired the following script, which combines all the previously described functionality into a single handler localGeoInfo.
The handler’s input argument is a record that may be coded with lat and lng properties specifying the location’s latitude and longitude in decimal form (as integers, real numbers, exponential numbers, or text representations of the same), or with an addr property specifying the location’s street or similar address as a text string. Error checking is performed on the input argument and the processing of the Google Maps API data.
The handler’s return value is a record with lat, lng, addr, and localtime properties. The first two properties are the input location’s latitude and longitude as real numbers, the next is the location’s address as a text string (as "best-guess"ed by the Google Maps API), and the last is the location’s local time as an Applescript date value. Note in the examples below the slight tweaking of the output location during the processing of the input location by the Google Maps API.
In keeping with my prior entries, the handler is a shell script solution that utilizes Python for downloading and processing of the url data from the Google Maps API, although it could of course be coded in AppleScriptObjC . Since Python relies on indentation for script parsing, it is important to preserve the leading tabs in the indented Python lines when copying the code.
Examples:
localGeoInfo({lat:40.7593755, lng:-73.9799726}) --> {lat:40.7593755, lng:-73.9799726, addr:"Rockefeller Plaza, 45 Rockefeller Plaza, New York, NY 10111, USA", localtime:date "Thursday, September 7, 2017 at 11:58:20 PM"}
localGeoInfo({addr:"30 Rockefeller Plaza, New York, NY 10112, USA"}) --> {lat:40.7593755, lng:-73.9799726, addr:"Rockefeller Plaza, 45 Rockefeller Plaza, New York, NY 10111, USA", localtime:date "Thursday, September 7, 2017 at 11:58:20 PM"}
Handler:
on localGeoInfo(inputRecord)
try
if inputRecord's class ≠ record then error number 999
tell (inputRecord & {lat:null, lng:null, addr:null}) to set {lat, lng, addr} to {its lat, its lng, its addr}
try
{lat as number, lng as number}
set isLatLng to true
on error
if addr's class ≠ text then error number 999
set isLatLng to false
end try
tell (do shell script "" & ¬
"python -c '" & linefeed & ¬
"import json, string, time, urllib" & linefeed & ¬
"epochTime=int(time.time())" & linefeed & ¬
"isLatLng = " & (isLatLng as integer) & " == 1" & linefeed & ¬
"theLat = theLng = theAddr = theTime = \"\"" & linefeed & ¬
"try:" & linefeed & ¬
tab & "if isLatLng:" & linefeed & ¬
tab & tab & "theUrl = \"https://maps.googleapis.com/maps/api/geocode/json?latlng=\" + (\"%.9f\" % " & lat & ") + \",\" + (\"%.9f\" % " & lng & ")" & linefeed & ¬
tab & "else:" & linefeed & ¬
tab & tab & "theUrl = \"https://maps.googleapis.com/maps/api/geocode/json?address=\" + string.replace(\"" & addr & "\", \" \", \"+\")" & linefeed & ¬
tab & "x = json.loads(urllib.urlopen(theUrl).read())[\"results\"]" & linefeed & ¬
tab & "y = x[0]" & linefeed & ¬
tab & "for z in x:" & linefeed & ¬
tab & tab & "if \"street_address\" in set(z[\"types\"]):" & linefeed & ¬
tab & tab & tab & "y = z" & linefeed & ¬
tab & tab & tab & "break" & linefeed & ¬
tab & "theLat = y[\"geometry\"][\"location\"][\"lat\"]" & linefeed & ¬
tab & "theLng = y[\"geometry\"][\"location\"][\"lng\"]" & linefeed & ¬
tab & "theAddr = y[\"formatted_address\"]" & linefeed & ¬
tab & "theUrl = \"https://maps.googleapis.com/maps/api/timezone/json?location=\" + str(theLat) + \",\" + str(theLng) + \"&\" + \"timestamp=\" + str(epochTime)" & linefeed & ¬
tab & "x = json.loads(urllib.urlopen(theUrl).read())" & linefeed & ¬
tab & "theTime = time.strftime(\"%Y %m %d %k %M %S\", time.gmtime(epochTime+int(x[\"dstOffset\"])+int(x[\"rawOffset\"])))" & linefeed & ¬
"except: pass" & linefeed & ¬
"if theLat == theLng == theAddr == theTime == \"\":" & linefeed & ¬
tab & "if isLatLng: raise Exception(\"Could not get location information for the input latitude and longitude.\")" & linefeed & ¬
tab & "else: raise Exception(\"Could not get location information for the input address.\")" & linefeed & ¬
"print(theLat)" & linefeed & ¬
"print(theLng)" & linefeed & ¬
"print(theAddr).encode(\"utf-8\")" & linefeed & ¬
"print(theTime)" & linefeed & ¬
"'")'s paragraphs to set {theLat, theLng, theAddr, {theYear, theMonth, theDay, theHour, theMinute, theSecond}} to {(item 1) as real, (item 2) as real, item 3, item 4's words}
tell (current date) to set {theTime, its day, its year, its month, its day, its hours, its minutes, its seconds} to {it, 1, theYear, theMonth, theDay, theHour, theMinute, theSecond}
on error m number n
if n = 999 then error "The input argument is invalid." & return & return & "It must be a record in either of the following forms:" & return & return & tab & "{lat:...latitude in decimal form..., lng:...longitude in decimal form...}" & return & tab & tab & "-OR-" & return & tab & "{addr:...address as a text string...}"
set o to offset of "Exception: " in m
if o > 0 then error (get m's text (o + 11) thru -1)
error m number n
end try
return {lat:theLat, lng:theLng, addr:theAddr, localtime:theTime}
end localGeoInfo
Edit notes:
- The script has been modified from that originally submitted. Previously, the latitude, longitude, and address location data was obtained from the first entry of the JSON “results” array. Now, the “results” array is searched in a for loop for an entry whose “types” array includes a “street_address” entry. If one is found, that “results” entry is used for the location data; otherwise, the first “results” array entry is used. This modification should improve the accuracy of the returned location data in certain cases of multiple conflicting location entries.
- The Python print(theAddr) statement was modified to handle non-ASCII characters in addresses properly.