Skip to content

Writing GPS EXIF information #6657

@cpraschl

Description

@cpraschl

What did you do?

I am trying to geo-reference images and for this I want to add GPS Exif data to the images.
While reading and writing using Pillow seems to work fine, the written GPS Exif Data seems to be invalid and is not accessible in other programs such as the Windows File Property overview.

What did you expect to happen?

When I write GPS Exif that it is also readable for other tools.

What actually happened?

GPS Exif is not available in other programs such as Windows File Properties (left image created with Pillow; right image created with camera) :
image

What are your OS, Python and Pillow versions?

  • OS: Windows 10
  • Python: 3.9
  • Pillow: 9.2.0

Sample code

import math
from typing import Tuple

from PIL import Image
from PIL.ExifTags import GPSTAGS

def truncate(f: float, n: int = 0) -> float:
    """
    Method for truncating a float value to n digits without rounding
    :param f: to be truncated
    :param n: number of digits
    :return: truncated float
    """
    return math.floor(f * 10 ** n) / 10 ** n

def get_dms_from_decimal(decimal: float) -> Tuple[float, float, float]:
    """
    Convert decimal value to DMS (degrees, minutes, seconds) tuple
    :param decimal: to be converted
    :return:
    """
    degrees = truncate(decimal, 0)
    minutes_whole = (decimal - degrees) * 60
    minutes = truncate(minutes_whole, 0)
    seconds = (minutes_whole - minutes) * 60
    return degrees, minutes, seconds


def get_decimal_from_dms(dms: Tuple[float, float, float], ref: str = "N") -> float:
    """
    Method for converting a DMS (degrees, minutes, seconds) tuple to a decimal value
    :param dms: to be converted
    :param ref: compass direction (N, E, S, W)
    :return: decimal representation
    """
    degrees = dms[0]
    minutes = dms[1] / 60.0
    seconds = dms[2] / 3600.0

    if ref in ['S', 'W']:
        degrees = -degrees
        minutes = -minutes
        seconds = -seconds

    return degrees + minutes + seconds


class GpsExifWriter:
    """
    Class allowing to write GPS exif data to an image
    """

    def get_gps(self, image_path: str) -> Tuple[float, float, float]:
        """
        Read gps EXIF information form image
        :param image_path: from which EXIF should be read
        :return: (lat, lng, alt) tuple
        """
        image = Image.open(image_path)
        exif = image.getexif()
        gps_info = exif.get_ifd(34853)
        gps_exif = {
            GPSTAGS.get(key, key): value
            for key, value in gps_info.items()
        }
        gps_latitude = gps_exif.get("GPSLatitude")
        gps_longitude = gps_exif.get("GPSLongitude")
        if gps_longitude is None or gps_latitude is None:
            raise Exception("No GPS information available in image")
        gps_latitude_ref = gps_exif.get("GPSLatitudeRef") or "N"
        gps_longitude_ref = gps_exif.get("GPSLongitudeRef") or "E"

        lat = get_decimal_from_dms(gps_latitude, gps_latitude_ref)
        lng = get_decimal_from_dms(gps_longitude, gps_longitude_ref)
        alt = gps_exif.get("GPSAltitude") or 0

        return lat, lng, alt

    def write_gps(self, image_path: str, lng: float, lat: float, alt: float, camera_manufacturer: str, camera: str, ) -> None:
        """
        Method for writing GPS exif information to an image
        :param image_path: to which exif should be added
        :param lng: longitude
        :param lat: latitude
        :param alt: altitude
        :param camera_manufacturer: Manufacturer of the camera
        :param camera: Camera used
        :return: None
        """
        image = Image.open(image_path)
        exif = image.getexif()
        gps_value = {0: b'\x02\x03\x00\x00', 1: 'N', 2: get_dms_from_decimal(lat), 3: 'E', 4: get_dms_from_decimal(lng), 5: b'\x00', 6: alt, 9: 'A', 18: 'WGS-84\x00'}
        # TODO writing probably not 100% correct: Reading of written data is possible, but e.g. windows explorer does not show up GPS information
        exif[34853] = gps_value
        exif[271] = camera_manufacturer
        exif[272] = camera
        image.save(image_path, exif=exif)


if __name__ == '__main__':
    image_path = r"C:\public\some_image.jpg"
    writer = GpsExifWriter()
    lat = 48.370075472222226
    lng = 14.5132905
    alt = 563.874
    writer.write_gps(image_path, lng, lat, alt, "Manufacturer", "Camera")

    gps = writer.get_gps(image_path)

    if lat == gps[0] and lng == gps[1] and alt == gps[2]:
        print("Re-Read successful")
    else:
        print("Differences in read and written GPS")

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions