You are not logged in.
Pages:: 1
Hello,
I am working on a script that receives a file from Hazel, determines its GPS location using EXIFTOOL and converts it to decimal values to call the google geocode api for location info in JSON format. I have pieced the script together from examples online.
I have been searching through the forum to find an example that I can understand on how extra certain values from the JSON data.
The first half of the script works as expected and I can use "JSON" Location Helper to get the JSON file.
I can get the first, second, third records etc. but not values inside each record. The first record is usually the formatted_address of the location. This is helpful but I would like to favour points of interest, parks, trail info etc.
I would like to search the JSON file for the specific value using these keys and default to city if none are available.
This is an example of JSON file from google.
I believe this is something simple something very simple but do not think I am searching the correct terms.
Applescript:
{results:{{formatted_address:"Canyonlands National Park, Utah 84532, USA", plus_code:{global_code:"85CGF55H+WH"}, address_components:{{short_name:"Canyonlands National Park", long_name:"Canyonlands National Park", types:{"establishment", "point_of_interest", "transit_station"}}, {short_name:"San Juan County", long_name:"San Juan County", types:{"administrative_area_level_2", "political"}}, {short_name:"UT", long_name:"Utah", types:{"administrative_area_level_1", "political"}}, {short_name:"US", long_name:"United States", types:{"country", "political"}}, {short_name:"84532", long_name:"84532", types:{"postal_code"}}}, geometry:{viewport:{northeast:{lat:38.461120980292, lng:-109.819667019708}, southwest:{lat:38.458423019708, lng:-109.822364980292}}, location:{lat:38.459772, lng:-109.821016}, location_type:"GEOMETRIC_CENTER"}, place_id:"ChIJcWINmO8QSIcRBYifYqyRZwU", types:{"establishment", "point_of_interest", "transit_station"}}, {formatted_address:"Grand View Point Rd, Moab, UT 84532, USA", address_components:{{short_name:"Grand View Point Rd", long_name:"Grand View Point Road", types:{"route"}}, {short_name:"Moab", long_name:"Moab", types:{"locality", "political"}}, {short_name:"San Juan County", long_name:"San Juan County", types:{"administrative_area_level_2", "political"}}, {short_name:"UT", long_name:"Utah", types:{"administrative_area_level_1", "political"}}, {short_name:"US", long_name:"United States", types:{"country", "political"}}, {short_name:"84532", long_name:"84532", types:{"postal_code"}}}, geometry:{location_type:"GEOMETRIC_CENTER", viewport:{northeast:{lat:38.4611096, lng:-109.819628219708}, southwest:{lat:38.4549744, lng:-109.822326180292}}, |bounds|:{northeast:{lat:38.4611096, lng:-109.8200543}, southwest:{lat:38.4549744, lng:-109.8219001}}, location:{lat:38.4578519, lng:-109.8200927}}, place_id:"ChIJb0bEUe4QSIcRumj4B4HcOl8", types:{"route"}}, {formatted_address:"Moab, UT 84532, USA", postcode_localities:{"Castle Valley", "Moab"}, address_components:{{short_name:"84532", long_name:"84532", types:{"postal_code"}}, {short_name:"Moab", long_name:"Moab", types:{"locality", "political"}}, {short_name:"UT", long_name:"Utah", types:{"administrative_area_level_1", "political"}}, {short_name:"US", long_name:"United States", types:{"country", "political"}}}, geometry:{location_type:"APPROXIMATE", viewport:{northeast:{lat:38.8717289, lng:-109.0588149}, southwest:{lat:37.94981, lng:-110.0574079}}, |bounds|:{northeast:{lat:38.8717289, lng:-109.0588149}, southwest:{lat:37.94981, lng:-110.0574079}}, location:{lat:38.5719944, lng:-109.4735066}}, place_id:"ChIJkVfE9H3CR4cRimQaBsHawOA", types:{"postal_code"}}, {formatted_address:"San Juan County, UT, USA", address_components:{{short_name:"San Juan County", long_name:"San Juan County", types:{"administrative_area_level_2", "political"}}, {short_name:"UT", long_name:"Utah", types:{"administrative_area_level_1", "political"}}, {short_name:"US", long_name:"United States", types:{"country", "political"}}}, geometry:{location_type:"APPROXIMATE", viewport:{northeast:{lat:38.4999896, lng:-109.0410581}, southwest:{lat:36.9979031, lng:-111.4122939}}, |bounds|:{northeast:{lat:38.4999896, lng:-109.0410581}, southwest:{lat:36.9979031, lng:-111.4122939}}, location:{lat:37.4634157, lng:-109.7591675}}, place_id:"ChIJ9Tcq0CXRN4cR55BS0_WTG_4", types:{"administrative_area_level_2", "political"}}, {formatted_address:"Utah, USA", address_components:{{short_name:"UT", long_name:"Utah", types:{"administrative_area_level_1", "political"}}, {short_name:"US", long_name:"United States", types:{"country", "political"}}}, geometry:{location_type:"APPROXIMATE", viewport:{northeast:{lat:42.001618, lng:-109.0410581}, southwest:{lat:36.9979031, lng:-114.0529979}}, |bounds|:{northeast:{lat:42.001618, lng:-109.0410581}, southwest:{lat:36.9979031, lng:-114.0529979}}, location:{lat:39.3209801, lng:-111.0937311}}, place_id:"ChIJzfkTj8drTIcRP0bXbKVK370", types:{"administrative_area_level_1", "political"}}, {formatted_address:"United States", address_components:{{short_name:"US", long_name:"United States", types:{"country", "political"}}}, geometry:{location_type:"APPROXIMATE", viewport:{northeast:{lat:71.5388001, lng:-66.885417}, southwest:{lat:18.7763, lng:170.5957}}, |bounds|:{northeast:{lat:71.5388001, lng:-66.885417}, southwest:{lat:18.7763, lng:170.5957}}, location:{lat:37.09024, lng:-95.712891}}, place_id:"ChIJCzYy5IS16lQRQrfeQ5K5Oxw", types:{"country", "political"}}, {formatted_address:"85CGF55H+GX", plus_code:{global_code:"85CGF55H+GX"}, address_components:{{short_name:"85CGF55H+GX", long_name:"85CGF55H+GX", types:{"plus_code"}}}, geometry:{location_type:"ROOFTOP", viewport:{northeast:{lat:38.460161480292, lng:-109.818713519708}, southwest:{lat:38.457463519708, lng:-109.821411480292}}, |bounds|:{northeast:{lat:38.458875, lng:-109.82}, southwest:{lat:38.45875, lng:-109.820125}}, location:{lat:38.4588056, lng:-109.8200444}}, place_id:"GhIJX5xTJLo6Q0ARJtGBm3t0W8A", types:{"plus_code"}}}, status:"OK", plus_code:{global_code:"85CGF55H+GX"}}
Offline
You can access nested values with the help of Objective-C's key paths.
Assuming thePath contains the path to a file containing the JSON data you can get the values of all "lat" keys with
Applescript:
use AppleScript version "2.5"
use framework "Foundation"
use scripting additions
property || : a reference to current application
set jsonData to ||'s NSData's dataWithContentsOfFile:thePath
set {jsonDictionary, jsonError} to ||'s NSJSONSerialization's JSONObjectWithData:jsonData options:0 |error|:(reference)
if jsonError is not missing value then error (jsonError's localizedDescription()) as text
set results to jsonDictionary's objectForKey:"results"
set allLats to (results's valueForKeyPath:"geometry.viewport.northeast.lat") as list
Last edited by StefanK (2021-02-19 03:19:48 am)
regards
Stefan
Offline
Thank you I will have to read up on this so I can understand it a little better.
Hopefully I will get some time this evening to work on it.
For my clarification, JSON helper or Location helper only make it easier to get the whole JSON file into AppleScript? They do not help with parse individual values from inside the JSON file? I thought my syntax was wrong since I could extract "formatted_address" but whenever I tried to change it to something else it failed.
Applescript:
set api_key to "XXXXXXXXXXXXXXXXXXX"
tell application "Location Helper"
set jsonData to reverse geocode location using coordinates {latDecimal, lonDecimal} with API key api_key as string
set jsonCountry to formatted_address of item 1 of results of jsonData
end tell
return {hazelOutputAttributes:{jsonCountry}}
Offline
A light just turned on for how to call for specific items using the valueForKeyPath.
I will have a closer look later.
Offline
You have to convert the AppleScript dictionary to an AppleScriptObjC (Foundation) dictionary to be able to use `valueForKeyPath`
Applescript:
set foundationDictionary to current application's NSDictionary's dictionaryWithDictionary:applescriptDictionary
regards
Stefan
Offline
Here is my script. I get the error that
error "-[__NSDictionaryM getFileSystemRepresentation:maxLength:]: unrecognized selector sent to instance 0x6000023b7b20" number -10000
Applescript:
set ExifTool to "/usr/local/bin/exiftool"
set ExifGPSLat to "-GPSLatitude"
set ExifGPSLon to "-GPSLongitude"
set pathList to ""
try
-- Manual
set pathList to quoted form of POSIX path of (choose file with prompt "Please choose a file:" default location (path to desktop))
-- Hazel
--set pathList to quoted form of POSIX path of (theFile as alias)
end try
-- Get Camera Model
--set cameraModel to do shell script ExifTool & " -p '${model}' " & pathList
--Get GPS Latitude and Longitude from EXIF data
do shell script ExifTool & space & ExifGPSLat & space & "-overwrite_original_in_place -P" & space & pathList
set gpsLat to result
do shell script ExifTool & space & ExifGPSLon & space & "-overwrite_original_in_place -P" & space & pathList
set gpsLon to result
--
if (gpsLat = "") or (gpsLon = "") then
-- error number -128
set trail to ""
set POI to ""
set country to ""
set state to "no gps"
set cityTown to ""
set jsonCountry to ""
return {hazelPassesScript:false, hazelOutputAttributes:{trail, POI, state, country, cameraModel}}
else
-- Set GPS variables separately
set latDegree to word 3 of gpsLat
set latMinute to word 5 of gpsLat
set latSecond to word 6 of gpsLat
set latDirection to word 7 of gpsLat
set lonDegree to word 3 of gpsLon
set lonMinute to word 5 of gpsLon
set lonSecond to word 6 of gpsLon
set lonDirection to word 7 of gpsLon
-- Convert Deg,Sec,Min to Decimals
set latDecimal to latDegree + latMinute / 60 + latSecond / 3600
set lonDecimal to lonDegree + lonMinute / 60 + lonSecond / 3600
-- Change Decimals based on Direction
if (latDirection is equal to "S") then
set latDecimal to latDecimal * -1 as number --text
end if
if (lonDirection is equal to "W") then
set lonDecimal to lonDecimal * -1 as number --text
end if
-- SET GOOGLE API KEY
set api_key to "XXXXXXXXXXXXXXXXX"
tell application "Location Helper"
set thePath to reverse geocode location using coordinates {latDecimal, lonDecimal} with API key api_key as string
end tell
end if
use AppleScript version "2.5"
use framework "Foundation"
use scripting additions
property || : a reference to current application
set jsonData to ||'s NSData's dataWithContentsOfFile:thepath
set {jsonDictionary, jsonError} to ||'s NSJSONSerialization's JSONObjectWithData:jsonData options:0 |error|:(reference)
if jsonError is not missing value then error (jsonError's localizedDescription()) as text
set results to jsonDictionary's objectForKey:"results"
set allLats to (results's valueForKeyPath:"geometry.viewport.northeast.lat") as list
return {hazelOutputAttributes:{allLats}}
Last edited by jfish (2021-02-19 03:20:25 pm)
Offline
With the use of AppleScriptObjC do I still need to parse the file with JSON helper or will the following code be enough?
I read somewhere about passing dataWithContentsOfFile vs the dataWithContentsOfURL. Since I am passing the google api url directly should I be using OfFile or OfURL?
Using OfFile I get the error
error "data parameter is nil" number -10000
Using OfURL I get the error
error "-[__NSCFString isFileURL]: unrecognized selector sent to instance 0x600001c8e490" number -10000
Alternatively returning jsonData returns missing value.
Applescript:
set api_key to "XXXXXXXXXXXXXXX"
set locatep to "38.4588056" & "," & "-109.8200444" as string
set thepath to "https://maps.googleapis.com/maps/api/geocode/json?latlng=" & locatep & "&key=" & api_key
use AppleScript version "2.5"
use framework "Foundation"
use scripting additions
property || : a reference to current application
set jsonData to ||'s NSData's dataWithContentsOfFile:thepath
set {jsonDictionary, jsonError} to ||'s NSJSONSerialization's JSONObjectWithData:jsonData options:0 |error|:(reference)
if jsonError is not missing value then error (jsonError's localizedDescription()) as text
set results to jsonDictionary's objectForKey:"results"
set allLats to (results's valueForKeyPath:"geometry.viewport.northeast.lat") as list
return allLats
Last edited by jfish (2021-02-22 10:42:08 pm)
Offline
The API dataWithContentsOfFile is for file systems paths.
You have to create an URL and use dataWithContentsOfURL
Applescript:
set thepath to "https://maps.googleapis.com/maps/api/geocode/json?latlng=" & locatep & "&key=" & api_key
set theURL to ||'s NSURL's URLWithString:thepath
set jsonData to ||'s NSData's dataWithContentsOfURL:theURL
regards
Stefan
Offline
Thank you, I have this working now but was hoping I could ask one more question.
In your example you are drilling down level by level to extract all of the latitudes inside northeast inside viewport inside geometry for each item which I understand.
Applescript:
set allLats to (results's valueForKeyPath:"geometry.viewport.northeast.lat") as list
However, how would you extract the long_name of specific "types" if the record resides at the same level? For example if I wanted to extract the long_name of administrative_area_level_1 or I wanted to search for Park or Establishment.
"results" : [
{
"address_components" : [
{
"long_name" : "West Rim Trail",
"short_name" : "W Rim Trail",
"types" : [ "route" ]
},
{
"long_name" : "Hurricane",
"short_name" : "Hurricane",
"types" : [ "locality", "political" ]
},
{
"long_name" : "Washington County",
"short_name" : "Washington County",
"types" : [ "administrative_area_level_2", "political" ]
},
{
"long_name" : "Utah",
"short_name" : "UT",
"types" : [ "administrative_area_level_1", "political" ]
},
{
"long_name" : "United States",
"short_name" : "US",
"types" : [ "country", "political" ]
},
{
"long_name" : "84737",
"short_name" : "84737",
"types" : [ "postal_code" ]
}
I really appreciate your help. I have been trying to figure something efficient out off and on for a long time.
Offline
Unfortunately due to the nested arrays it's not possible to search with key paths.
This is an example with a loop, asDict represents the AppleScript dictionary in your first post.
longNames will contain all long names whose types contain administrative_area_level_1
Applescript:
use AppleScript version "2.5"
use framework "Foundation"
use scripting additions
property || : a reference to current application
set cocoaDict to ||'s NSDictionary's dictionaryWithDictionary:asDict
set results to cocoaDict's objectForKey:"results"
set longNames to {}
repeat with anItem in results
set addressComponents to (anItem's objectForKey:"address_components")
repeat with aComponent in addressComponents
set componentTypes to (aComponent's objectForKey:"types")
if (componentTypes's containsObject:"administrative_area_level_1") then
set end of longNames to (aComponent's objectForKey:"long_name") as text
end if
end repeat
end repeat
regards
Stefan
Offline
Stefan, thank you this is great.
I have been testing it and playing with the types returned and works like a charm inside AppleScript Editor. Thank you very much.
Are you familiar with Hazel? I cannot get the script to compile inside the Hazel handler
Applescript:
on hazelMatchFile(theFile, inputAttributes)
--Code
return {hazelPassesScript:true, hazelOutputAttributes:{}}
The script gets stuck on the following lines. If I comment out the handler all is well.
Applescript:
use AppleScript version "2.5"
use framework "Foundation"
use scripting additions
property || : a reference to current application
Here is my final script.
Applescript:
on hazelMatchFile(theFile, inputAttributes)
set ExifTool to "/usr/local/bin/exiftool"
set ExifGPSLat to "-GPSLatitude"
set ExifGPSLon to "-GPSLongitude"
set ExifToolOption to "-keywords="
set pathList to ""
set TagTool to "/usr/local/bin/tag"
set TagToolOption to "-s" -- The set operation reTrails all tags on the specified files with one or more new tags.
set TagToolNOGPS to "NO GPS Information"
try
-- Manual
--set pathList to quoted form of POSIX path of (choose file with prompt "Please choose a file:" default location (path to desktop))
-- Hazel
set pathList to quoted form of POSIX path of (theFile as alias)
end try
-- Get Camera Model
--set cameraModel to do shell script ExifTool & " -p '${model}' " & pathList
--Get GPS Latitude and Longitude from EXIF data
do shell script ExifTool & space & ExifGPSLat & space & "-overwrite_original_in_place -P" & space & pathList
set gpsLat to result
do shell script ExifTool & space & ExifGPSLon & space & "-overwrite_original_in_place -P" & space & pathList
set gpsLon to result
--do shell script TagTool & space & TagToolOption & quoted form of TagToolNOGPS & space & pathList
-- CONVERT GPS COORDINDATES TO DECIMAL
if (gpsLat = "") or (gpsLon = "") then
-- error number -128
set trail to ""
set poi to ""
set country to ""
set state to "no gps"
set cityTown to ""
set jsonCountry to ""
return {hazelPassesScript:false, hazelOutputAttributes:{trail, poi, state, country, cameraModel}}
else
-- Set GPS variables separately
set latDegree to word 3 of gpsLat
set latMinute to word 5 of gpsLat
set latSecond to word 6 of gpsLat
set latDirection to word 7 of gpsLat
set lonDegree to word 3 of gpsLon
set lonMinute to word 5 of gpsLon
set lonSecond to word 6 of gpsLon
set lonDirection to word 7 of gpsLon
-- Convert Deg,Sec,Min to Decimals
set latDecimal to latDegree + latMinute / 60 + latSecond / 3600
set lonDecimal to lonDegree + lonMinute / 60 + lonSecond / 3600
-- Change Decimals based on Direction
if (latDirection is equal to "S") then
set latDecimal to latDecimal * -1 as number --text
end if
if (lonDirection is equal to "W") then
set lonDecimal to lonDecimal * -1 as number --text
end if
set locateP to latDecimal & "," & lonDecimal as string
-- SET GOOGLE API KEY
set api_key to "AIzaSyCewHXy9mUtqRzUAIY036xxDoAh-26JJys"
set thePath to "https://maps.googleapis.com/maps/api/geocode/json?latlng=" & locateP & "&key=" & api_key
tell application "Location Helper"
set asDict to reverse geocode location using coordinates {latDecimal, lonDecimal} with API key api_key
end tell
--tell application "Safari" to open location thePath
end if
use AppleScript version "2.5"
use framework "Foundation"
use scripting additions
property || : a reference to current application
set cocoaDict to ||'s NSDictionary's dictionaryWithDictionary:asDict
set results to cocoaDict's objectForKey:"results"
set poi to {}
set poiTemp to {}
set route to {}
set routeTemp to {}
set cityTown to {}
set cityTownTemp to {}
set stateProvince to {}
set stateProvinceTemp to {}
set country to {}
set countryTemp to {}
repeat with anItem in results
set addressComponents to (anItem's objectForKey:"address_components")
repeat with aComponent in addressComponents
set componentTypes to (aComponent's objectForKey:"types")
if (componentTypes's containsObject:"point_of_interest") then
set end of poiTemp to (aComponent's objectForKey:"long_name") as text
set poi to item 1 of poiTemp
else
set componentTypes to (aComponent's objectForKey:"types")
if (componentTypes's containsObject:"establishment") then
set end of poiTemp to (aComponent's objectForKey:"long_name") as text
set poi to item 1 of poiTemp
else
set componentTypes to (aComponent's objectForKey:"types")
if (componentTypes's containsObject:"park") then
set end of poiTemp to (aComponent's objectForKey:"long_name") as text
set poi to item 1 of poiTemp
else
set componentTypes to (aComponent's objectForKey:"types")
if (componentTypes's containsObject:"natural_feature") then
set end of poiTemp to (aComponent's objectForKey:"long_name") as text
set poi to item 1 of poiTemp
else
set componentTypes to (aComponent's objectForKey:"types")
if (componentTypes's containsObject:"colloquial_area") then
set end of poiTemp to (aComponent's objectForKey:"long_name") as text
set poi to item 1 of poiTemp
end if
end if
end if
end if
end if
if (componentTypes's containsObject:"route") then
set end of routeTemp to (aComponent's objectForKey:"long_name") as text
set route to item 1 of routeTemp
else
if (componentTypes's containsObject:"premise") then
set end of routeTemp to (aComponent's objectForKey:"long_name") as text
set route to item 1 of routeTemp
end if
end if
if (componentTypes's containsObject:"locality") then
set end of cityTownTemp to (aComponent's objectForKey:"long_name") as text
set cityTown to item 1 of cityTownTemp
end if
if (componentTypes's containsObject:"administrative_area_level_1") then
set end of stateProvinceTemp to (aComponent's objectForKey:"long_name") as text
set stateProvince to item 1 of stateProvinceTemp
end if
if (componentTypes's containsObject:"country") then
set end of countryTemp to (aComponent's objectForKey:"long_name") as text
set country to item 1 of countryTemp
end if
end repeat
end repeat
return {hazelPassesScript:true, hazelOutputAttributes:{route, poi, cityTown, stateProvince, country}}
Offline
Pages:: 1