Tuesday, April 13, 2021

#1 2021-02-18 11:25:49 pm

jfish
Member
From:: Canada
Registered: 2019-07-09
Posts: 16

Return specific values fromJSON Location Helper File (google geocode)

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

 

#2 2021-02-19 03:15:55 am

StefanK
Member
From:: St. Gallen, Switzerland
Registered: 2006-10-21
Posts: 11746
Website

Re: Return specific values fromJSON Location Helper File (google geocode)

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

 

#3 2021-02-19 08:08:22 am

jfish
Member
From:: Canada
Registered: 2019-07-09
Posts: 16

Re: Return specific values fromJSON Location Helper File (google geocode)

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

 

#4 2021-02-19 08:09:48 am

jfish
Member
From:: Canada
Registered: 2019-07-09
Posts: 16

Re: Return specific values fromJSON Location Helper File (google geocode)

A light just turned on for how to call for specific items using the valueForKeyPath.

I will have a closer look later.

Offline

 

#5 2021-02-19 10:07:30 am

StefanK
Member
From:: St. Gallen, Switzerland
Registered: 2006-10-21
Posts: 11746
Website

Re: Return specific values fromJSON Location Helper File (google geocode)

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

 

#6 2021-02-19 02:48:47 pm

jfish
Member
From:: Canada
Registered: 2019-07-09
Posts: 16

Re: Return specific values fromJSON Location Helper File (google geocode)

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

 

#7 2021-02-22 10:32:05 pm

jfish
Member
From:: Canada
Registered: 2019-07-09
Posts: 16

Re: Return specific values fromJSON Location Helper File (google geocode)

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

 

#8 2021-02-23 05:37:28 am

StefanK
Member
From:: St. Gallen, Switzerland
Registered: 2006-10-21
Posts: 11746
Website

Re: Return specific values fromJSON Location Helper File (google geocode)

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

 

#9 2021-02-23 05:49:17 pm

jfish
Member
From:: Canada
Registered: 2019-07-09
Posts: 16

Re: Return specific values fromJSON Location Helper File (google geocode)

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

 

#10 2021-02-24 09:42:22 am

StefanK
Member
From:: St. Gallen, Switzerland
Registered: 2006-10-21
Posts: 11746
Website

Re: Return specific values fromJSON Location Helper File (google geocode)

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

 

#11 2021-02-24 10:22:37 pm

jfish
Member
From:: Canada
Registered: 2019-07-09
Posts: 16

Re: Return specific values fromJSON Location Helper File (google geocode)

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

 

#12 2021-02-25 03:03:06 am

StefanK
Member
From:: St. Gallen, Switzerland
Registered: 2006-10-21
Posts: 11746
Website

Re: Return specific values fromJSON Location Helper File (google geocode)

I'm afraid I'm not familiar with Hazel


regards

Stefan

Offline

 

Board footer

Powered by FluxBB

RSS (new topics) RSS (active topics)