| HALO-Photographs | GPS-Script for Expression Media | HALO-Photographs |
Script to retrieve textual, geodetic information from www.geonames.org |
|
Recently, when searching for an alternative to enter placenames into my digital asset management catalogs running with the aid of
Expression Media2 by Microsoft, I came across a reveiling and interesting discussion in the forum for Expression Media2. Following that discussion and working with that VB-script, I soon found this script to be the base for the alternative I seeked for. However, since Expression Media2 allows to geocode images but not the reverse processs, I decided to adapt the script so that it also reads
that newly entered, annotated GPS-Data from images. This GPS-Data is kept inside the IPTC-fields for Longitude and Latidude only and not
within the EXIF section that may contain more valuable geodetic information.
'Script to retrieve geodetic information from geonames.org
'Script derived from:
'- (dead url) social.expression.microsoft.com/forums/en/media/thread/1728cf60-c8c8-4d4a-b805-19ab5451fded
'- original by Felix Andrew / download the code at: (dead url) go.microsoft.com/fwlink/?LinkId=116859
'- modification1 by John Beardsworth, GB / view the code at: http://www.beardsworth.co.uk/pics/blog/Country_State_City_Location_from_GPS.vbs.txt
'- modification2 by Tom Bartl / http://social.expression.microsoft.com/Profile/en-US/?user=TomBartl
'- modification3 by Hans Loepfe, Switzerland / https://www.halo-photographs.comscripts/GPS-to-textual-geodectic-information.html
' corrected GPS Data conversion from DDD.MM.SS.SS to DD.ddddddd in original script
'- source: written by http://www.fcc.gov/mb/audio/bickel/DDDMMSS-decimal.html
'------------------------
'current functionalities:
'------------------------
'checks for availability of GPS-Data in EXIF- and in IPTC-fields
'if found, select using GPS-Data from EXIF or IPTC
'function validation for degrees > 0 and < 360
'copies GPS-Data from EXIF into IPTC
'processed images are labeled by means of a defineable keyword
'non-processed images are labeled with - Label# 8, light-blue (adjustable, optional)
'reverse geocoding is adjusted to 1st use findNearby (fN) and then findNearbyPlaceName(fNPN) because fN holds a more specific placename than fNPN, which holds a placename of a broader scope.
'Timezone values are written into custom_field 'TimeZoneGMT' as offset value to GMT and also the placename, both referring to GMT (DST optional).
'Country specific adjustments for City and Region (for now US and CH only)
'option to display full results (XML) of actual querry in browser
Option Explicit
const BoxTitle = "Microsoft Expression Media"
const BoxTitle1 = "Reverse geocode images online via geonames.org"
'Images that have no GPS-Data and thus cannot be reverse geocoded will be labeled
'you can choose a Labelnumber (0-9) according to your needs
const Labelnumber = 8 'light-blue
'Images that were successfully reverse-geocoded receive the keyword assigned here
const kw_success = "reverse_geocoded_by_script" 'optional, adjustable
Dim app, mediaItems, mediaItem, x, y, EXIF, IPTC, switch, longitude, latitude, debug
debug = "no" 'to display the full results of the actual querry, set this to "yes" and copy the contents in the box into the browser URL
'debug = "yes"
Dim strLong, strLat 'variables for function to convert DD.MM.SS.SS (Degrees) to DD.dddddd (Degrees.decimal)
'display description and EXIF or IPTC question boxes
if ( MsgBox("Regular Execution (YES) / Debug mode (NO) ?", 4, BoxTitle1) = vbYes ) then
debug = "no"
Main()
else
debug = "yes"
Main()
end if
'Main()
sub Main()
set app = CreateObject("ExpressionMedia.Application")
set mediaItems = app.ActiveCatalog.MediaItems
'reset result counters to 0
x = 0000
y = 0000
for each mediaItem in mediaItems
if( mediaItem.Selected ) then
'check for value in EXIF
if( mediaItem.DeviceInfo.Longitude = "" ) then
'check for value IPTC in degrees: 000, empty,
if( mid(mediaItem.Annotations.Longitude,3,3) = "000" ) then 'check if the image has been processed before using the the alternative option below
'do not attempt to reverse geocode this image, as there is no GPS Data in it
mediaItem.LabelIndex = Labelnumber 'item gets labeled
x = x + 1 'number of images without GPS-Data
switch = "nogps"
elseif mediaItem.Annotations.Longitude ="" then
'do not attempt to reverse geocode this image, as there is no GPS Data in it
mediaItem.LabelIndex = Labelnumber
x = x + 1
switch = "nogps"
'alternatively write 0 values into Long & Lat for items without GPS-Data (aka Gulf of New Guinea)
'mediaItem.Annotations.Latitude = 0 'option
'mediaItem.Annotations.Longitude = 0 'option
else 'run subroutine for images with GPS-Data held in IPTC-fields
switch = "iptc"
GPSIPTC
end If
elseif ( MsgBox("Use Lat/Long from IPTC- instead of EXIF-Data ? (IPTC = YES) / EXIF = NO ", 4, BoxTitle1) = vbYes ) then
switch = "iptc"
GPSIPTC
else 'run subroutine for images with GPS-Data held in EXIF-fields
'( mediaItem.DeviceInfo.Longitude <> "" ) then
switch = "exif" 'GPS-Data in EXIF has priority over GPS-Data in IPTC
GPSEXIF
end if
'msgbox switch,,"switch2"
y = y + 1 'total number of images processed
end if
next
MsgBox y-x & " - reverse geocoded" & vbCrLf & x & " - NOT reverse geocoded" & vbCrLf & y & " - processed", vbOKOnly, "Result"
end sub
'Function to convert GPS-Data into decimals
'This function is accurate as rounding errors are taken into account.
'0.2 is not the same as 0.2000284
'Function adapted by Hans Loepfe, Switzerland in July 2008
'source: written by http://www.fcc.gov/mb/audio/bickel/DDDMMSS-decimal.html
'calculating the LATITUDE
Function DegLat(value)
dim latsign, dLat, dLat1, dLat2, dLat3, dLat4
dim absdlat, absmlat, absslat, alat
dlat = split(strLat, " ")
dLat1 = Left(dLat(1), 3)
dLat2 = CSng(Left(dLat(2), 2))
dLat3 = CSng(Left(dLat(3), 5))
'NORTH or SOUTH
if (dLat(0)) = "S" then 'checking for northern (+) or southern (-) Hemisphere
latsign = -1
else
latsign = 1
end if
'DEGREES
absdlat = abs(round(dLat1 * 1000000.)) ' 'round' is used to eliminate the small error caused by rounding in the computer. e.g. 0.2 is not the same as 0.20000000000284
'Error check
if (absdlat > (90 * 1000000)) then
msgbox ("Degrees Latitude must be in the range of -90 to 90.")
end if
'MINUTES
dLat2 = abs(round(dLat2 * 1000000.)/1000000)
absmlat = abs(round(dLat2 * 1000000.))
'Error check
if (absmlat >= (60 * 1000000)) then
msgbox ("Minutes Latitude must be in the range of 0 to 59.")
end if
'SECONDS
dLat4 = round(dlat3 * 1000000.)/1000000
absslat = abs(round(dLat4 * 1000000.)) 'Note: kept as big integer for now, even if submitted as decimal
'Error check
if (absslat > (59.99999999 * 1000000)) then
msgbox ("Minutes Latitude must be 0 or greater and less than 60.")
end if
'CONVERT Latidude Degrees to decimals
DegLat = round(absdlat + (absmlat/60.) + (absslat/3600.) ) * latsign/1000000
end function
'calculating the LONGITUDE
function DegLong(value)
dim lonsign, dlon, dlon1, dlon2, dlon3, dlon4
dim absdlon, absmlon, absslon, alon
dlon = split(strLong, " ")
dlon1 = Left(dlon(1), 3)
dlon2 = CSng(Left(dlon(2), 2))
dlon3 = CSng(Left(dlon(3), 5))
'EAST or WEST
if (dLon(0)) = "W" then 'checking for eastern (+) or western (-) Hemisphere
lonsign = -1
else
lonsign = 1
end if
'DEGREES
absdlon = abs(round(dlon1 * 1000000.)) ' 'round' is used to eliminate the small error caused by rounding in the computer. e.g. 0.2 is not the same as 0.20000000000284
'Error check
if (absdlon > (180 * 1000000)) then
msgbox ("Degrees Longitude must be in the range of -180 to 180.")
end if
'MINUTES
dlon2 = abs(round(dlon2 * 1000000.)/1000000)
absmlon = abs(round(dlon2 * 1000000.))
'Error check
if (absmlon >= (60 * 1000000)) then
msgbox ("Minutes Longitude must be in the range of 0 to 59.")
end if
'SECONDS
dlon4 = round(dlon3 * 1000000.)/1000000
absslon = abs(round(dlon4 * 1000000.)) 'Note: kept as big integer for now, even if submitted as decimal
'Error check
if (absslon > (59.99999999 * 1000000)) then
msgbox ("Minutes Longitude must be 0 or greater and less than 60.")
end if
'CONVERT longitude Degrees to decimals
DegLong = round(absdlon + (absmlon/60.) + (absslon/3600.) ) * lonsign/1000000
end function
'Process images with GPS-Data in EXIF
sub GPSEXIF()
if (switch = "exif") then
strLong = mediaItem.DeviceInfo.Longitude
strLat = mediaItem.DeviceInfo.Latitude
msgbox strLong
longitude = DegLong( strLong )
latitude = DegLat( strLat )
'copy the GPS-Data from EXIF into IPTC, this ensures that all processed images have GPS-Data in the IPTC section
'GPS-Data in EXIF has priority over GPS-Data in IPTC
'Disable the following 2 lines only, if the above is not your preference and enable the 2 disabled lines below.
mediaItem.Annotations.Longitude = mediaItem.DeviceInfo.Longitude 'enable / disable
mediaItem.Annotations.Latitude = mediaItem.DeviceInfo.Latitude 'enable / disable
'copying the values calculated by the function above clearly reveal rounding errors as a fraction of seconds (0.0x)
'this equivalent to about 0.88m or 3 feet only (at LAT 38°)
'see also http://commons.wikimedia.org/wiki/Commons:Geocoding#Precision
'mediaItem.Annotations.Longitude = Longitude 'enable / disable
'mediaItem.Annotations.Latitude = Latitude 'enable / disable
Image 'retrieve geodetic properties from www.Geonames.org
end if
end sub
'Process images with GPS-Data in IPTC
sub GPSIPTC()
if ( switch = "iptc" ) then
strLong = mediaItem.Annotations.Longitude
strLat = mediaItem.Annotations.Latitude
longitude = DegLong( strLong )
latitude = DegLat( strLat )
Image 'retrieve geodetic properties from www.Geonames.org
end if
end sub
'Retrieve geodetic information from http://www.geonames.org for images with GPS-Data in EXIF or IPTC
sub Image()
if (switch <> "nogps") then
dim xmlhttp, xmldom, nbySuffix, querry
dim geoIdNode, locationNode, isoCodeNode, countryNode, cityNode, regionNode, city_region, stateNode
dim TZ1, TZGMT, TZDST, TZID, timezoneNode
dim fNPN_location1, fN_location2, geoId0, fNPN_geoId1, fN_geoId2
dim timezoneNodeGMT, timezoneNodeDST, timezoneNodeID
dim fNPN_isoCode
nbySuffix = "&style=full"
'querry = ""
set xmlhttp = CreateObject("Microsoft.XMLHTTP")
'findNearby (fN)
'first run a narrower search of data for the selected item
'uncomment following line for adjustments - copy the contents into the browser URL
if ( debug = "yes" ) then
inputbox "findNearby", , "http://ws.geonames.org/findNearby?lat="+CStr(latitude)+"&lng="+CStr(longitude) & nbySuffix
inputbox "findNearbyWikipedia", , "http://ws.geonames.org/findNearbyWikipedia?lat="+CStr(latitude)+"&lng="+CStr(longitude) & nbySuffix
end if
xmlhttp.open "GET", "http://ws.geonames.org/findNearby?lat="+CStr(latitude)+"&lng="+CStr(longitude) & nbySuffix , False
set xmldom = CreateObject("Microsoft.XMLDOM")
xmlhttp.send xmldom
xmldom.loadXML(xmlhttp.responseText)
set locationNode = xmldom.selectSingleNode("geonames/geoname/name")
'store location node for comparison below
fN_location2 = locationNode.text
set geoIDNode = xmldom.selectSingleNode("geonames/geoname/geonameId")
'store geoID node for comparison below
fN_geoId2 = geoIdNode.text
' set isoCodeNode = xmldom.selectSingleNode("geonames/geoname/countryCode") 'not needed here
' set countryNode = xmldom.selectSingleNode("geonames/geoname/countryName") 'not needed here
set cityNode = xmldom.selectSingleNode("geonames/geoname/adminName1")
set regionNode = xmldom.selectSingleNode("geonames/geoname/adminName2")
set stateNode = xmldom.selectSingleNode("geonames/geoname/adminName3")
'findNearbyPlaceName (fNPN)
'a broader search of data as a 2nd step for the selected item
'this querry will overwrite data already retrieved with 'findNearby (fN)
'but that's ok becuase the PlaceName found is stored in the variable fN-Location2
'uncomment following line for adjustments - copy the contents into the browser URL
if ( debug = "yes" ) then
inputbox "findNearbyPlaceName", , "http://ws.geonames.org/findNearbyPlaceName?lat="+CStr(latitude)+"&lng="+CStr(longitude) & nbySuffix
inputbox "extendedFindNearby", , "http://ws.geonames.org/extendedFindNearby?lat="+CStr(latitude)+"&lng="+CStr(longitude)
end if
xmlhttp.open "GET", "http://ws.geonames.org/findNearbyPlaceName?lat="+CStr(latitude)+"&lng="+CStr(longitude) & nbySuffix, False
set xmldom = CreateObject("Microsoft.XMLDOM")
xmlhttp.send xmldom
xmldom.loadXML(xmlhttp.responseText)
set locationNode = xmldom.selectSingleNode("geonames/geoname/name")
'store location node for comparison below
fNPN_location1 = locationNode.text
set geoIDNode = xmldom.selectSingleNode("geonames/geoname/geonameId")
'store geoID node for comparison below
fNPN_geoId1 = geoIdNode.text
'msgbox fNPN_geoID1
set isoCodeNode = xmldom.selectSingleNode("geonames/geoname/countryCode")
'store isoCode node for comparison below
fNPN_isoCode = isoCodeNode.text
set countryNode = xmldom.selectSingleNode("geonames/geoname/countryName")
set cityNode = xmldom.selectSingleNode("geonames/geoname/name")
if ( fNPN_isoCode = "US" ) then
set regionNode = xmldom.selectSingleNode("geonames/geoname/adminName2")
elseif (fNPN_isoCode = "CH" ) then
set regionNode = xmldom.selectSingleNode("geonames/geoname/adminName3")
end if
set stateNode = xmldom.selectSingleNode("geonames/geoname/adminName1")
set timezoneNode = xmldom.selectSingleNode("geonames/geoname/timezone")
'store timezone value for verification below
TZ1 = timezoneNode.text
'timezone (TZ) - get timezone information from another request to geonames.org
'uncomment following line for adjustments - copy the contents into the browser URL
'inputbox "timezone", , "http://ws.geonames.org/timezone?lat="+CStr(latitude)+"&lng="+CStr(longitude)
xmlhttp.open "GET", "http://ws.geonames.org/timezone?lat="+CStr(latitude)+"&lng="+CStr(longitude), False
set xmldom = CreateObject("Microsoft.XMLDOM")
xmlhttp.send xmldom
xmldom.loadXML(xmlhttp.responseText)
set timezoneNodeGMT = xmldom.selectSingleNode("geonames/timezone/gmtOffset")
set timezoneNodeDST = xmldom.selectSingleNode("geonames/timezone/dstOffset")
set timezoneNodeID = xmldom.selectSingleNode("geonames/timezone/timezoneId")
TZGMT = timezoneNodeGMT.text
TZDST = timezoneNodeDST.text
TZID = timezoneNodeID.text
'verifying timezone querries
if ( TZ1 <> TZID ) then
msgbox "For some reason Timezone values do NOT correspond",,BoxTitle
end if
'else
'do nothing as both querries retrieved the same data
'end if
'findNearbyWikipedia (fNW)
'first run a narrower search of data for the selected item
'uncomment following line for adjustments - copy the contents into the browser URL
if ( debug = "yes" ) then
inputbox "findNearbyWikipedia", , "http://ws.geonames.org/findNearbyWikipedia?lat="+CStr(latitude)+"&lng="+CStr(longitude) & nbySuffix
end if
dim summaryNode, wikiUrlNode, fNW_summary, fNW_wikiURL, exist_description, fNW_description
xmlhttp.open "GET", "http://ws.geonames.org/findNearbyWikipedia?lat="+CStr(latitude)+"&lng="+CStr(longitude) & nbySuffix , False
set xmldom = CreateObject("Microsoft.XMLDOM")
xmlhttp.send xmldom
xmldom.loadXML(xmlhttp.responseText)
set summaryNode = xmldom.selectSingleNode("geonames/entry/summary")
'store location node for comparison below
fNW_summary = summaryNode.text
set wikiUrlNode = xmldom.selectSingleNode("geonames/entry/wikipediaUrl")
'store geoID node for comparison below
fNW_wikiUrl = wikiUrlNode.text
fNW_description = fNW_summary & "(source: " & fNW_wikiUrl &")"
'writing retrieved data into the EM2 catalog
'1st: compare values for location and geoId
if (fNPN_location1 <> fN_location2) then
mediaItem.Annotations.Location=fN_location2
end if
'else
'do nothing as we want to further specify the location manually and also to avoid double entries
'mediaItem.Annotations.Location=fNPN_location1
'end if
if (fNPN_geoId1 <> fN_geoId2) then
geoId0 = fN_geoId2
else
geoId0 = fNPN_geoId1
end if
'2nd: concatenate (gmtOffset/timezoneId/genameId) into one custom field named TimeZoneGMT
'mediaItem.customFields.item("TimeZoneGMT").value = timezoneNode.text
'mediaItem.customFields.item("TimeZoneGMT").value = TZDST & "/" & TZID & "/" & geoId0 'optional
mediaItem.customFields.item("TimeZoneGMT").value = TZGMT & "/" & TZID & "/" & geoId0
'3rd: differentiate 'cityNode' between isoCode= US and CH
if ( fNPN_isoCode = "US" ) then
city_region = fNPN_location1 & ", " & regionNode.text 'combine City, Region for US only
mediaItem.Annotations.city = city_region
elseif (fNPN_isoCode = "CH" ) then
mediaItem.Annotations.City=cityNode.text
end if
'mediaItem.Annotations.Region=regionNode.text
'4th: descision about description entry
exist_description = mediaItem.Annotations.Caption 'store exisiting description
if ( exist_description <> "" ) then
dim result
result = msgbox("Exisiting:" & vbCrLf & exist_description & vbCrLf & vbCrLf & "Wiki:" &vbCrLf & fNW_description & vbCrLf & vbCrLF & "Add Wiki to exisiting description (YES) "& vbCrLf & "Wiki only, overwrite existing description (NO)" & vbCrLf & "Keep exisiting description (CANCEL) ?", 3, "Descriptions")
'msgbox result 'check button push
select case result
case VbYes 'result = 6
mediaItem.Annotations.Caption = exist_description & " - " & fNW_description 'add Wiki-Description to existing Description
case VbNo 'result = 7
mediaItem.Annotations.Caption = fNW_description 'overwrite exisiting and write Wiki-Description
case VbCancel 'result = 2 'do nothing = keep exisitng
end select
else
'fNW_description = inputbox( "findNearbyWikipedia", , fNW_description) 'option to edit the Wiki-Description
mediaItem.Annotations.Caption = fNW_description
end if
'5th: write the rest into the EM2 catalog
mediaItem.Annotations.State=stateNode.text
mediaItem.Annotations.Country=countryNode.text
mediaItem.Annotations.CountryCode=isoCodeNode.text
'optional keyword assignment
mediaItem.Annotations.Keywords = kw_success 'enable / disable
'Stuff to be added or completed
'1- Altidude in meters could be requested equally using the service astergdem at www.geonames.org
' uncomment following line for adjustments - copy the contents into the browser URL
'inputbox "altitude", , "http://ws.geonames.org/astergdem?lat="+CStr(latitude)+"&lng="+CStr(longitude)
'2- put url into WEB-Page code to verify location on WEB published images
'inputbox "display1", , "http://www.geonames.org/maps/google_"+CStr(latitude)+"_"+CStr(longitude)+".html"
mediaItem.Annotations.Url = "http://www.geonames.org/maps/google_"+CStr(latitude)+"_"+CStr(longitude)+".html"
' better to use
'inputbox "display1", , "http://www.geonames.org/"+CStr(geoId0)
'mediaItem.Annotations.Url = "http://www.geonames.org/"+CStr(geoId0)
'3- genaue CH Placenames und Flurnamen auf map.geo.admin.ch finden
' umrechnen von CH-X/Y-Koordinaten(UTM) into GPS-Lat/Long-Koordinaten
end if
end sub
resources: (URL no longer available) discussion in the forum for Expression Media2 |