Convert string output to array

I have some image data from exiftool (via cfexecute) that I want to store or use to map a location. I need latitude and longitude.

I can get the data from exiftool no problem. An example of what I get is:

GPS Latitude Ref : North GPS Longitude Ref : West GPS Altitude Ref : Above Sea Level GPS Speed Ref : km/h GPS Speed : 0.1428571429 GPS Img Direction Ref : True North GPS Img Direction : 200.6229705 GPS Dest Bearing Ref : True North GPS Dest Bearing : 200.6229705 GPS Horizontal Positioning Error: 4.813172661 m GPS Altitude : 130 m Above Sea Level GPS Latitude : 45 deg 27' 45.21" N GPS Longitude : 122 deg 51' 37.02" W GPS Position : 45 deg 27' 45.21" N, 122 deg 51' 37.02" W

All I really need is lat/long and maybe position, preferably as an array

array[I][1] = Latitude
array[I][2] = Longitude
array[I][3] = Position

GPS Latitude : 45 deg 27’ 45.21" N
GPS Longitude : 122 deg 51’ 37.02" W
GPS Position : 45 deg 27’ 45.21" N, 122 deg 51’ 37.02" W

And I feel like this is something I should know how to do but my string manipulation skills are failing me.

For example:
‘‘writeDump(serialize(exifData));’’
Doesn’t to the job.

SerializeJSON, etc isn’t giving me what I want or need.

Or, I’m just not familiar enough with this to make it work. Anyone have some suggestions for this?

it’s line delimited?

arr =  ListToArray( src, chr( 10 ) );

st = [=]; // ordered struct

arrayEach( arr, function( item ) {
   st [ trim( listFirst( item, ":" ) ) ] = trim( listRest( item, ":" ) ) ;
})

``

Yes, but with \n instead of \r.

I will certainly try your example.

Zack, thanks for the great response! I managed to get what I needed. I’ll provide some more info here on the project as well. Perhaps you or others can give some constructive tips?

First, can you explain your code a bit? It did work, and it allowed me to see what my prior error was - I had made the assumption that I was creating a two dimensional array when it was just one. But the compact nature (C++ ?) of your code is not something I have used before.

arr = ListToArray( standardOut, chr( 10 ) );

Create a basic array, char(10) delimiter

st = [=]; // ordered struct

Is this from C++?

I had to google that line and got: “In the context of C++, st = [=] defines a lambda expression where st is likely a variable or identifier, and [=] is the [capture clause]. The [=] capture clause indicates that all variables used within the lambda’s body should be captured by value. This means the lambda creates a copy of the external variables it uses, rather than directly accessing the originals.”

arrayEach( arr, function( item ) { st [ trim( listFirst( item, ":" ) ) ] = trim( listRest( item, ":" ) ) ;})

Populate the array. And I have used listFirst, listRest before. But it’s the
function( item )
syntax that is new to me and how that works.

Of course it 100% worked and gave me:

Now the final version I need is a bit more formatted. So I add a little more processing (no doubt verbose). Here is the full script:

<!--- string output, not structured, gives GPS data if found --->
<cfexecute name="bash" arguments="/opt/bash-exiftool.sh -a -G -gps* #imageToUse#" timeout="2400" variable="standardOut" errorVariable="returnedError"></cfexecute>
<cfdump var="#standardOut#">

<br />
<br />

<!--- zac's simplified and effecient code; produces simple array with one item per row --->
<cfscript>
arr =  ListToArray( standardOut, chr( 10 ) );

st = [=]; // ordered struct

arrayEach( arr, function( item ) {
   st [ trim( listFirst( item, ":" ) ) ] = trim( listRest( item, ":" ) ) ;
})
</cfscript>
<cfdump var="#arr#">

<br />
<br />


<!--- my verbose code; produces GPS item name and value per row --->
<cfset standardOut=#replaceNoCase(standardOut, "[", "", "ALL")# />
<cfset standardOut=#replaceNoCase(standardOut, "]", "", "ALL")# />
<cfset standardOut=#replaceNoCase(standardOut, " ; ", ":", "ALL")# />

<!--- single item array from shell scritpt output --->
<cfset standardOutArray = ListToArray( standardOut, chr( 10 ) )>

<!--- array to hold newly structured data from standardOutArray --->
<cfset lattLongArray = arraynew(2) />

<!--- populate lattLongArray ---> 
<cfloop from="1" to="#arrayLen(standardOutArray)#" index="i">
	<cfset lattLongArray[i][1] = trim( listFirst( standardOutArray[i], ":" ) )  />
	<cfset lattLongArray[i][2] = trim( listRest( standardOutArray[i], ":" ) ) />
</cfloop>

<!--- output lattLongArray --->
<cfdump var="#lattLongArray#">

Which gives me:

That’s the final form I am looking for.

The project is a basic photo catalog for myself because after testing out NextCloud, Piwigo, Immich (via Docker), and Photoprism, each has its own problems and issues.

So, Lucee (CF) facilitates image data quite easily:

<cfset exifData = imageGetEXIFMetadata(imageToUse)>
<cfscript>
	writeDump(serialize(exifData));
</cfscript>

But it doesn’t work with HEIC images. So, I need a way to read the HEIC data from hundreds/thousands of images which is where CFEXECUTE and exiftool come in. And ImageMagick can convert HEIC to JPG when needed.

So, that’s the thing I am trying to do:

  1. Use Lucee to read a directory of folders and subfolders with images;
  2. Display the image (use other tools to convert HEIC if needed);
  3. Send EXIF data to Openstreetmaps or Google map
  4. Display the map
1 Like

modern (and not so modern) cfml is nice, eh?

that struct syntax has been around for a while

There’s no free native java library for HEIF yet, but there are commercial options

thanks for taking the time to write that all up, I’m sure other people will benefit!

1 Like

Thanks! And thanks for not shaming me (LOL). I love CF but never had the background of other languages so I consider myself a competent hack.

Always happy to help!

Curious, how could your code (below) be changed to create a 2 dimensional array?

<cfscript>
arr =  ListToArray( standardOut, chr( 10 ) );

st = [=]; // ordered struct

arrayEach( arr, function( item ) {
   st [ trim( listFirst( item, ":" ) ) ] = trim( listRest( item, ":" ) ) ;
})
</cfscript>

If you don’t want to give away the answer, a hint is fine. I’ts how I learn.

What do you want the array to look like?

https://dev.lucee.org/uploads/default/original/2X/3/3344527083b9d6a54af8c43b7551e693d0ad3d46.png

I’m curious how much more efficiently I could have done it.

I’m curious, why do you prefer that to an ordered struct?

Familiarity? When you say “ordered struct” that’s how I view that output. But maybe I am missing the term. I like that format because I’ve used that format so often to iterate through things like a shopping cart, or inserting things into the database. Example:

arrayName [ index/row ][ column ][ column ][ column ]

I might be putting several expense items in one related overall expense by looping through that structure to do this:

insert into tableName (expenseID,  storeName, item,  cost) 
values (......

array row 1 data = 12345, Target, item, cost
array row 2 data = 12345, Walmart, item, cost
array row 3 data = 12345, Costco, item, cost

Just used to that . Better method?

an ordered struct maintains the insert order, otherwise, the order of a struct should be treated as random (cfdump sorts them on display).

you can loop over a struct using

structEach(st, function( key, value, st ){
// do something
});

or

loop collection=#st# key="key" value="value" {
   // do something
}

or

for (var el in st) {
   // do something
}

Arrays are also fine, I just find structs more flexible as you can reference them by keys. Arrays are much faster but you can only loop thru them manually.

Honestly, what ever works is fine!

What I considered is most important, the code is clean and understandable, coding is telling a story

2 Likes

Awesome! Thanks Zack - you’ve always been awesome and so has everyone I’ve interacted with here. My comment about not shaming me was really mean in jest, regarding my old school CF coding. Thanks again!

I’ve been using [:] for ordered structs for years, but today’s the first time I’ve seen [=] for the same. Why are there two different shorthands for ordered structs?

dunno! @micstriit would know