Grabbing Geodata From Your Photo Library Using Python | by Aaron S | Towards Data Science

So one of the many talents my girlfriend has is photography. However she has over 100,000 photos which require sorting out primarily into year and city. She primarily does location based photography and finds writing essays about certain areas of the world easier when she has all of them in one place.
This is a mammoth task and something she has put off during her working life. However now that COVID has put us in a position where these necessary tasks which get pushed back normally now she has decided to address.
So I decided to help her out by using my knowledge of Python which seemed well suited to the task.
To be able to grab meta-data from jpegsTo be able to use the Google geocode API to reverse geocode to obtain address information based on longitudes and latitudesTo use this meta-data to obtain city and country informationTo be able to sort out images by year and country for further photo editing
To be able to grab meta-data from jpegs
To be able to use the Google geocode API to reverse geocode to obtain address information based on longitudes and latitudes
To use this meta-data to obtain city and country information
To be able to sort out images by year and country for further photo editing
First we will take a look at the structure of the program and the packages we will use to create the script.
Obtain a list of the images to sortObtain photo metadata (Longitude and latitude, date of photo taken)Grab location based on longitude/latitudeCreate folders based on yearCreate folders within each year based on cityMove images into folders based on year and city
Obtain a list of the images to sort
Obtain photo metadata (Longitude and latitude, date of photo taken)
Grab location based on longitude/latitude
Create folders based on year
Create folders within each year based on city
Move images into folders based on year and city
GPSPhoto: Used to grab metadata from jpeg’sSelenium: Used to access location data based on longitude and latitudestime: Used to slow the process selenium uses to grab dataOs: Used to create folders with pythonShutil: Used to move files into newly created folders
GPSPhoto: Used to grab metadata from jpeg’s
Selenium: Used to access location data based on longitude and latitudes
time: Used to slow the process selenium uses to grab data
Os: Used to create folders with python
Shutil: Used to move files into newly created folders
Now we have the structure of how we are to proceed lets take each part in term.
Using python’s os module it is easy to create a list of files.
import osimage_list = os.listdir('c:\\users\\Aaron\\photo')image_list = [a for a in image_list if a.endswith('jpg')]
Notes1. The os module is imported2. The image_list variable is defined. The listdirmethod of the os module is used and we specify the location of the images. Note \\ is used to allow us to create the string of a folder location. This is called an escape character. 3. We create a list and only include files that end in ‘.jpg’. Here we’re using a list comprehension, and the endswith string method to include only ‘jpg.
There are lots of packages out there to obtain this data. I found GPSPhoto to be a simple way to obtain exactly what I needed in a clear way. We use the getgpsdata method which we specify the filepath of the image to obtain a dictionary of longitudes and latitudes, along with when the photo is taken.
from GPSPhoto import gpsphotofor a in image_list:      data = gpsphoto.getGPSData(os.getcwd() + f'\\{a}')     print(data)
Output:
{'Latitude': 55.85808611111111, 'Longitude': -4.291505555555555, 'Altitude': 10.32358870967742, 'UTC-Time': '17:41:5.29', 'Date': '08/23/2017'}
Notes1. We loop through the image_list and use the getcwd() method to obtain the current working directory and we concatenate this with a file image. In this case we are using the variable a. This is because we will be looping through our list of images and therefore need a general way to grab the metadata for each image no matter how many images are in the folder. See below for details!
2. Note we are using f-strings to do this, we can loop around our list of images and grab data on each image. Whatever goes in the {} gets passed as string, so in this case it will be each image file of the list we just created.
3. We’ve printed out the meta data we need to sort out the folders.
In the section above we grabbed the longitutde and latitude data. We need to convert this to obtain a location. For this we can use the geocode API provided by google. We get up to 200 dollars of free usage each month and at 5 dollars per 1000 requests. This isn’t a bad way to do quite a few number of images.
Please follow the instructions to gain access to the geocode API here . Remember you need to set up a billing account before getting access to the geocode API. Don’t worry money doesn’t get taken off you for giving them your details! But if you’re doing lots of requests please be careful!
In order to get the information we need, we must make a request using the url down below specifying the longitude and latitude. Please remember to insert the API key you got from following the guide above!
import requestslong = data['Longitude']lat = data['Latitude']response = requests.get(f'https://maps.googleapis.com/maps/api/geocode/json?latlng={lat},{long}&key=INSERT API KEY')print(response.json())
Output:
{'plus_code': {'compound_code': 'VP55+69 Glasgow, UK',  'global_code': '9C7QVP55+69'}, 'results': [{'address_components': [{'long_name': '21',     'short_name': '21',     'types': ['street_number']},    {'long_name': 'Pacific Quay',     'short_name': 'Pacific Quay',     'types': ['route']},    {'long_name': 'Glasgow',     'short_name': 'Glasgow',     'types': ['postal_town']},
When we make an API request we get this json file back! Now json behaves much like a dictionary and we can therefore access parts of the json received like wise.
Notes.1. We use the request get method with the API url. We then convert this usingh the json() method to the form we want it in.
json_response = response.json()city = json_response['results'][0]['address_components'][2]['long_name']
Notes1. We specify the first results key [results][0] and within that the [address_components] key. We use the second item within [address_components] and finally we want the value of key long_name
It’s best to go iteratively through this step by step to get the the data you want.
At this point we have a way to get metadata from a single image and we have our list of the images. But we need to be able to create folders based on the year of when the photo was taken. We use the os module with some string manipulation to achieve this.
year = data['Date'].split('/')[-1]year_path = os.getcwd() + '\\' + str(year)print(year_path)if os.path.isdir(f'{year}'):    return year_pathelse:    os.mkdir(data['Date'].split('/')[-1])    return year_path
Notes1. data[Date] is a dictionary value for the date the image is taken. We use the split list method to split this data into a list of day, month and year. The year will always be the last list and therefore we [-1] gives us the year we need. We use the getcwdmethod to gain the current working directory.
2. We use the isdir method to confirm there is a folder for the year calculated. We then return the value year_path. This piece of code will be turned into a function hence the returning of the data.
3. We use the mkdir and specify the folder name if the year folder hasn’t already been created, in this case it’s the year we just calculated.
We now have the city from the images meta data we can create the respective folder. But we want create the city folders as a subdirectory of the year.
location_path = year_path + '\\' + cityif os.path.isdir(f'{location_path}'):    print('City Folder already created')else:    os.mkdir(f'{year_path}' + '\\' + f'{city}')    print(f'City: {city} folder created ')
Notes1. Here we check for the location folder within each year.
2. We use the mkdir method as before if the location folder hasn’t already been created, but this time we use the year_path variable we created above and concatenate this with the city variable we just created. Simple as that!
Now we created a list of images filenames, and we said we’d be looping over them, this becomes important when we want to move the files to a specific location. We use the shutil package to move files with. The move method, takes two arguments, the absolute path of a file and the absolute path of where you want the file to be.
shutil.move(os.getcwd() + f'\\{a}' ,location_folder + f'\\{a}')
Notes1. We specify the variable a, which will be our image filename, we concatenate this to create the absolute path of the image. 2. We use the location_folder we created in the last section and concatenate this with the image file name to give the absolute path of the place we want the file to be.
Now with all the parts of the code written. We haven’t address the fact we will be looping around a list of image filenames. In my case, some of my girlfriend’s photos do not have metadata and therefore it was also necessary to code in that case, simply to continue lopping till images that do have meta-data are seen.
def address(data):    long = data['Longitude']    lat = data['Latitude']    response = requests.get(f'https://maps.googleapis.com/maps/api/geocode/json?latlng={lat},{long}&key=AIzaSyA6WyqENNQ0wbkNblLHgWQMCphiFmjmgHc')    json_response = response.json()    city = json_response['results'][0]['address_components'][2]['long_name']    return citydef year_folder(data):    year_path = os.getcwd() + '\\' + str(data['Date'].split('/')[-1])    print(year_path)    year = data['Date'].split('/')[-1]    if os.path.isdir(f'{year}'):        return year_path    else:        os.mkdir(data['Date'].split('/')[-1])        return year_pathdef location_folder(year_path,city):    location_path = year_path + '\\' + city    if os.path.isdir(f'{location_path}'):        print('City Folder already created')    else:        os.mkdir(f'{year_path}' + '\\' + f'{city}')        print(f'City: {city} folder created ')def move_images(year_path, city,a):    location_folder = year_path + f'\\{city}'    shutil.move(os.getcwd() + f'\\{a}',location_folder + f'\\{a}')
Notes1. The address function hasn’t changed much from what we’ve discussed above.2. The year_folder function check to see if the year fold has been created. If so, we don’t need to create one. The else part of the statement ensures that if a year folder doesn’t exist, we create one. 3. The location_folder function we create the absolute path for the location folder and we test to see if it already exists. If it hasn’t we create it.4. The move_images function we move the file from the original folder to the respective place in the specific year and city folder.
if __name__ == "__main__":image_list = os.listdir('c:\\users\\Aaron\\photo')image_list = [a for a in image_list if a.endswith('jpg')] print(image_list)for a in image_list:        data = gpsphoto.getGPSData(os.getcwd() + f'\\{a}')        print(data)        try:            if data['Latitude']:                city = address(data)        except:            print('Image has no Address')            continue        if data['Date']:            year_path = year_folder(data)        else:            print('Image has no Date Attached')            continue        location_folder(year_path,city)        move_images(year_path,city,a)        print('Image moved succesfully')
Notes1. The image_list variable uses the listdir method to create a list of all file names within the image directory.2. We loop over every image filename. We grab the meta data for each file and put it through two checks. If it has location meta-data, we grab that, if it has meta-data for when the photo was taken we grab that. 3. Subsequently if we have both location and date of photo taken, we create the year folder and the location folder. 4. We then move the images from the folder the files were originally in, to the folder’s we’ve created.
Using a few packages we have managed to create a script that could potentially sort thousands of photos out into the required folders for further photo editing and sorting! For full code please see here.
Please see here for further details about what I’m up to project-wise on my blog and other posts. For more tech/coding related content please sign up to my newsletter here
I’d be grateful for any comments or if you want to collaborate or need help with python please do get in touch.If you want to get in contact with me, please do so here asmith53@ed.ac.uk or on twitter.