Saturday, 27 July 2013

Taking the Grunt out of Geotags


Here's a matlab script to automatically compile all geo-tagged images from a directory and all its subdirectories into a Google Earth kml file showing positions and thumbnails

close all;clear all;clc
addpath('/path/where/googleearth/toolbox/is')
% get main directory (Projects directory)
pathFolder=uigetdir();
% get list of subfolders
d = dir(pathFolder);
isub = [d(:).isdir];
nameFolds = {d(isub).name}';
nameFolds(ismember(nameFolds,{'.','..'})) = [];
% prompt user to select which subfolders to process
[s,v] = listdlg('PromptString','Select folders:',...
'SelectionMode','multiple',...
'ListString',nameFolds);
% use only those directories
directories=char(nameFolds(s));
for md=1:size(directories,1)
% get list of subfolders
d = dir(strtrim([pathFolder,filesep,directories(md,:)]));
isub = [d(:).isdir];
nameFolds = {d(isub).name}';
nameFolds(ismember(nameFolds,{'.','..'})) = [];
subdirectories{md}=char(nameFolds);
end
mattime=[]; timestring=[]; lat=[]; lon=[]; filenames={};
direc=[];
counter=0;
for dd=1:size(directories,1)
for sd=1:size(subdirectories{dd},1)
if isempty(strmatch(strtrim(subdirectories{dd}(sd,:)),'dng')) &&...
isempty(strmatch(strtrim(subdirectories{dd}(sd,:)),'crw'))
files1=ReadImDir(strtrim([pathFolder,filesep,strtrim(directories(dd,:)),...
filesep,strtrim(subdirectories{dd}(sd,:))]),'JPG');
files2=ReadImDir(strtrim([pathFolder,filesep,strtrim(directories(dd,:)),...
filesep,strtrim(subdirectories{dd}(sd,:))]),'jpg');
files=char(files1,files2); %,files3
for k=1:size(files,1)
in=strtrim([pathFolder,filesep,strtrim(directories(dd,:)),filesep,strtrim(subdirectories{dd}(sd,:)),...
filesep,strtrim(files(k,:))]);
if isempty(regexp(in,'ppm.jpg', 'once'))
counter=counter+1;
filenames{counter}=in;
[stat,res]=system(['exiftool -h "',in,'"']);
latstr=res(regexp(res,'>GPS Latitude<')+22:regexp(res,'>GPS Latitude<')+41);
if ~isempty(latstr)
deg=str2double(strtrim(latstr(1:2)));
mins=str2double(strtrim(latstr(8:9)));
secs=str2double(strtrim(latstr(16:end)));
lat=[lat; deg+ mins/60 + secs/3600];
lonstr=res(regexp(res,'>GPS Longitude<')+22:regexp(res,'>GPS Longitude<')+42);
deg=str2double(strtrim(lonstr(2:4)));
mins=str2double(strtrim(lonstr(10:11)));
secs=str2double(strtrim(lonstr(17:end)));
lon=[lon; deg+ mins/60 + secs/3600];
timestr=res(regexp(res,'>GPS Date/Time<')+23:regexp(res,'>GPS Date/Time<')+41);
y=str2double(timestr(1:4)); m=str2double(timestr(6:7)); d=str2double(timestr(9:10));
H=str2double(timestr(12:13)); M=str2double(timestr(15:16)); S=str2double(timestr(18:19));
if S==0
S=S+1;
end
mattime=[mattime;datenum([y,m,d,H,M,S])];
timestring=[timestring;datestr(datenum([y,m,d,H,M,S]))];
direc=[direc;dd];
else
lat=[lat;NaN]; lon=[lon;NaN];
timestr=res(regexp(res,'>Create Date<')+21:regexp(res,'>Create Date<')+39);
y=str2double(timestr(1:4)); m=str2double(timestr(6:7)); d=str2double(timestr(9:10));
H=str2double(timestr(12:13)); M=str2double(timestr(15:16)); S=str2double(timestr(18:19));
if y==2012
y=y+1;
end
if S==0
S=S+1;
end
mattime=[mattime;datenum([y,m,d,H,M,S])];
timestring=[timestring;datestr(datenum([y,m,d,H,M,S]))];
direc=[direc;dd];
end
end % if not ppm.jpg
end %k
end % if not dng or crw
end %sd
end %dd
thumbfiles=cell(size(filenames));
for i=1:length(lat)
thumbfiles{i}=[filenames{i},'_thumb.jpg'];
end
kmlStr=cell(1,length(lat));
for i=1:length(lat)
system(['exiftool -b -ThumbnailImage ',filenames{i},' > ',thumbfiles{i}])
kmlStr{i} = ge_point_new(-lon(i),lat(i),0,...
'description',['<a href="',filenames{i},'">link to file</a>'] ,...
'iconURL',thumbfiles{i},...
'name',filenames{i}(regexp(filenames{i},'IMG'):end));
end
tmp=[];
for i=1:length(lat)
tmp=[tmp,eval(['kmlStr{',num2str(i),'}'])];
end
ge_output('points.kml',...
ge_folder('points',tmp));
system(['google-earth "',pwd,filesep,'points.kml" &'])
save('photo_res','mattime','lat','lon','direc','timestring','filenames','directories')
view raw geophotos2kml.m hosted with ❤ by GitHub


Like most of my matlab scripts, it's essentially just a wrapper for a system command. It requires exiftool for the thumbnail creation (available on both *nix and for Windows). It also needs the (free) Google Earth toolbox for Matlab.

Right now it's just set up for JPG/jpg files, but could easily be modified or extended. As well as the kml file (Google Earth will launch as soon as the script is finished) it will save a matlab format file contining the lats, longs, times and filenames.

Outputs look a little like this (a selection of over 30,000 photos taken in Grand Canyon)




Friday, 26 July 2013

A cooler shell prompt

In ~/.bashrc:

export PS1='\[\033[1;34m\]\t\[\033[0m\] \[\033[1;35m\]\u\[\033[0m\]:\[\033[1;35m\]\W\[\033[0m\] \[\033[1;92m\]$(__git_ps1 "(%s)")\[\033[0m\]$ '
view raw prompt.sh hosted with ❤ by GitHub


time: username: directory


much better!

Thursday, 25 July 2013

How to start a BASH script

Use chance!

# flip a coin
FLIP=$(($(($RANDOM%10))%2))
# if heads, use cowsay to present your splash
if [ $FLIP -eq 1 ]
then
# start with some wisdom
fortune -s | cowsay -n | zenity --text-info --title="Your message here" --width 500 --height 500
else
# or start with some other msg
figlet DAN ROCKS | zenity --text-info --title="Your message here" --width 600 --height 200
fi
view raw cow flip.sh hosted with ❤ by GitHub


Flip a coin. If heads, allow fortune to generate a quotation and cowsay to present it:



If tails, figlet your 'splash' message:


Discuss

Wednesday, 24 July 2013

Destination point given distance and bearing from start point

You have starting points:
xcoordinate, a list of x-coordinates (longitudes)
ycoordinate, a list of y-coordinates (latitudes)
num_samples, the number of samples in the plane towards the destination point

bearings:
heading, a list of headings (degrees)

and distances:

range, a list of distances in two directions (metres)

This is how you find the coordinates (x, y) along the plane defining a starting point and two end points, in MATLAB.

R = 6371 % mean radius of the Earth, in km
% pre-allocate for results
x=cell(1,num_points)
y=cell(1,num_points)
for k=1:num_points
% COORDINATES OF STARTING POINT
lon1=xcoordinate(k) ;
lat1=ycoordinate(k) ;
d=range(k)/1000; %range in km
b1=heading(k); % bearing in degrees
% FIND FINAL BEARING FROM INITIAL BEARING
if b1=>180
b2 = b1-180;
else
b2 = b1 + 180;
end
% CONVERT TO RADIANS
b1= b1.*(pi/180);
b2= b2.*(pi/180);
dOverR=d/R; % angular radians
% LATITUDE, END POINT 1
lat2 = asin( sin(lat1*(pi/180))*cos(dOverR) + ...
cos(lat1*(pi/180))*sin(dOverR)*cos(b1) ) / (pi/180);
% LONGITUDE, END POINT 1
lon2 = lon1 + atan2( sin(b1)*sin(dOverR)*cos(lat1), ...
cos(dOverR)-sin(lat1)*sin(lat2) ) ;
% LATITUDE, END POINT 2
lat3 = asin( sin(lat1*(pi/180))*cos(dOverR) + ...
cos(lat1*(pi/180))*sin(dOverR)*cos(b2) ) / (pi/180);
% LONGITUDE, END POINT 2
lon3 = lon1 + atan2( sin(b2)*sin(dOverR)*cos(lat1), ...
cos(dOverR)-sin(lat1)*sin(lat2) ) ;
% FIND COORDINATES IN THAT PLANE
y{k}=linspace( min([lat2,lat3]),max([lat2,lat3]), num_samples(k));
x{k}=linspace( min([lon2,lon3]),max([lon2,lon3]), num_samples(k));
end

Routine BASH memory aid

Some common things I do in BASH

#Do something if a file exists
if [ -f $target_dir"/$target_file" ]
then do something here
fi
#Calculate pi using bc
pi=`echo "4*a(1)" | bc -l`
#Convert degs to radians
rad=`echo "$deg*($pi/180)" | bc -l`
#Calculate tangent in degrees
tandeg=$(echo "s($rad)/c($rad)" | bc -l)
#List subdirectories in pwd
subdir=$(ls -d */)
#Clear screen
printf "\033c"
#Calling python to do a math operation rather than bc , e.g. a ceil:
round_up=$(python -c "from math import ceil; print ceil($variable)")
#Pass arguments to script from command line
args=("$@")
first_arg=${args[0]}
second_arg=${args[1]}
#Pass number lines in file to variable
numrecords=$(wc -l $target_dir"/file.ext" | awk -F" " '{print $1}')
#Delete every file which is not a *.ext file
find $target_dir -maxdepth 1 -type f ! -iname "*.ext" | xargs -I '{}' rm '{}'
#Pipe the first two columns of file to new file
awk '{print $1 " " $2 " " (-$3) }' $xyzfile".dat" > $xyzfile"new.dat"
#Max and min of a column (n) in file
n=2
lims=$(awk 'BEGIN{ max = -999999999 ; min = 9999999999 } $n > max {max = $n} $n < min {min = $n} END{ print min, max }' $file)
#Find a replace in file (NaNs with Os)
sed -i 's/NaN/0/g' file.ext
#Select a directory using zenity and pass to variable
export my_dir=`zenity --file-selection --title="Select a Directory" --directory`