|
|
|
"""Add geotagged images to a kml for viewing in Google Earth.
|
|
|
|
|
|
|
|
This script takes a folder of images, and creates a placemark with a thumbnail
|
|
|
|
of each image, based on the GPS coordinates in the EXIF tags.
|
|
|
|
|
|
|
|
Usage:
|
|
|
|
photo_to_kml [-h] IMAGE_FOLDER
|
|
|
|
|
|
|
|
Positional arguments:
|
|
|
|
IMAGE_FOLDER name of input folder
|
|
|
|
|
|
|
|
Optional arguments:
|
|
|
|
-h, --help show this help message and exit
|
|
|
|
|
|
|
|
Examples:
|
|
|
|
|
|
|
|
Create a kml for a folder of photos, named 'image_folder'.
|
|
|
|
> photo_to_kml image_folder
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
import os
|
|
|
|
import sys
|
|
|
|
import argparse
|
|
|
|
from PIL import Image
|
|
|
|
from PIL.ExifTags import TAGS, GPSTAGS
|
|
|
|
import simplekml
|
|
|
|
|
|
|
|
|
|
|
|
def get_exif_data(image):
|
|
|
|
"""
|
|
|
|
Return a dictionary from the exif data of an PIL Image object, and convert
|
|
|
|
the GPS uags.
|
|
|
|
https://gist.github.com/valgur/2fbed04680864fab1bfc
|
|
|
|
"""
|
|
|
|
|
|
|
|
info = image._getexif()
|
|
|
|
if not info:
|
|
|
|
return {}
|
|
|
|
exif_data = {TAGS.get(tag, tag): value for tag, value in info.items()}
|
|
|
|
|
|
|
|
def is_fraction(val):
|
|
|
|
return isinstance(val, tuple) and len(val) == 2 and isinstance(
|
|
|
|
val[0], int) and isinstance(val[1], int)
|
|
|
|
|
|
|
|
def frac_to_dec(frac):
|
|
|
|
return float(frac[0]) / float(frac[1])
|
|
|
|
|
|
|
|
if 'GPSInfo' in exif_data:
|
|
|
|
gpsinfo = {
|
|
|
|
GPSTAGS.get(t, t): v
|
|
|
|
for t, v in exif_data['GPSInfo'].items()
|
|
|
|
}
|
|
|
|
for tag, value in gpsinfo.items():
|
|
|
|
if is_fraction(value):
|
|
|
|
gpsinfo[tag] = frac_to_dec(value)
|
|
|
|
elif all(is_fraction(x) for x in value):
|
|
|
|
gpsinfo[tag] = tuple(map(frac_to_dec, value))
|
|
|
|
exif_data['GPSInfo'] = gpsinfo
|
|
|
|
|
|
|
|
return exif_data
|
|
|
|
|
|
|
|
|
|
|
|
def get_lat_lon(exif_data):
|
|
|
|
"""
|
|
|
|
Return the latitude and longitude (if available) from exif_data.
|
|
|
|
https://gist.github.com/valgur/2fbed04680864fab1bfc
|
|
|
|
"""
|
|
|
|
lat = None
|
|
|
|
lon = None
|
|
|
|
gps_info = exif_data.get('GPSInfo')
|
|
|
|
|
|
|
|
def convert_to_degrees(value):
|
|
|
|
d, m, s = value
|
|
|
|
return d + (m / 60.0) + (s / 3600.0)
|
|
|
|
|
|
|
|
if gps_info:
|
|
|
|
gps_latitude = gps_info.get('GPSLatitude')
|
|
|
|
gps_latitude_ref = gps_info.get('GPSLatitudeRef')
|
|
|
|
gps_longitude = gps_info.get('GPSLongitude')
|
|
|
|
gps_longitude_ref = gps_info.get('GPSLongitudeRef')
|
|
|
|
|
|
|
|
if (gps_latitude and gps_latitude_ref and gps_longitude and
|
|
|
|
gps_longitude_ref):
|
|
|
|
lat = convert_to_degrees(gps_latitude)
|
|
|
|
if gps_latitude_ref == 'S':
|
|
|
|
lat = -lat
|
|
|
|
|
|
|
|
lon = convert_to_degrees(gps_longitude)
|
|
|
|
if gps_longitude_ref == 'W':
|
|
|
|
lon = -lon
|
|
|
|
|
|
|
|
return lat, lon
|
|
|
|
|
|
|
|
|
|
|
|
def export_kml_file(dirname, fnames, dirname_thumbs, kml_name):
|
|
|
|
"""
|
|
|
|
Create the kml document
|
|
|
|
"""
|
|
|
|
kml = simplekml.Kml()
|
|
|
|
|
|
|
|
for fname in fnames:
|
|
|
|
print('Reading {}...'.format(fname))
|
|
|
|
with Image.open(os.path.join(dirname, fname)) as image:
|
|
|
|
# Get EXIF tags
|
|
|
|
exif_data = get_exif_data(image)
|
|
|
|
|
|
|
|
# Save thumbnail
|
|
|
|
image.thumbnail((128, 128))
|
|
|
|
image.save(os.path.join(dirname_thumbs, fname), 'jpeg')
|
|
|
|
|
|
|
|
lat, lon = get_lat_lon(exif_data)
|
|
|
|
pnt = kml.newpoint(name=fname)
|
|
|
|
pnt.coords = [(lon, lat)]
|
|
|
|
|
|
|
|
# Add popup window with full-size image
|
|
|
|
pnt.description = (
|
|
|
|
'<![CDATA[ <img src="{}"" height="500px" />]]>'.format(
|
|
|
|
os.path.join(dirname, fname)))
|
|
|
|
|
|
|
|
# Show groups of photos as camera icon
|
|
|
|
pnt.stylemap.normalstyle.iconstyle.scale = 1
|
|
|
|
pnt.stylemap.normalstyle.iconstyle.icon.href = (
|
|
|
|
'http://maps.google.com/'
|
|
|
|
'mapfiles/kml/shapes/camera.png')
|
|
|
|
|
|
|
|
# Show placemark as image thumbnail
|
|
|
|
pnt.stylemap.highlightstyle.iconstyle.scale = 3
|
|
|
|
pnt.stylemap.highlightstyle.iconstyle.icon.href = os.path.join(
|
|
|
|
dirname_thumbs, fname)
|
|
|
|
|
|
|
|
kml.save(kml_name)
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
parser = argparse.ArgumentParser()
|
|
|
|
parser.add_argument(
|
|
|
|
'folder',
|
|
|
|
metavar='IMAGE_FOLDER',
|
|
|
|
help='name of input folder',
|
|
|
|
default=None)
|
|
|
|
|
|
|
|
# Print usage if no arguments are provided
|
|
|
|
if len(sys.argv) == 1:
|
|
|
|
parser.print_help(sys.stderr)
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
|
|
# Get files in image directory
|
|
|
|
ext = ['.jpg', '.jpeg', '.tif', '.tiff']
|
|
|
|
dirname = args.folder
|
|
|
|
fnames = [
|
|
|
|
f for f in os.listdir(dirname)
|
|
|
|
if os.path.splitext(f)[-1].lower() in ext
|
|
|
|
]
|
|
|
|
|
|
|
|
# Create thumbnails folder beside originals
|
|
|
|
dirname_thumbs = os.path.join(os.path.dirname(dirname), 'thumbs')
|
|
|
|
try:
|
|
|
|
os.mkdir(dirname_thumbs)
|
|
|
|
except FileExistsError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
# Create kml file
|
|
|
|
kml_name = dirname + '.kml'
|
|
|
|
export_kml_file(dirname, fnames, dirname_thumbs, kml_name)
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|