Jan 15, 2009

 

A Python Script to Make a Google Earth KMZ

 

This script makes thumbnails of jpg images using Python PIL, then writes a KML file based on data provided to group photos by location, finally zipping it all up into a self-contained KMZ file.

 

Taking photographs while traveling is a very common activity. I do it for both research and pleasure. Google Earth is a great way to showcase photographs for locations you visit, or locations in a series along a path. In Google Earth, a discrete location can be set with a placemark, for which you can provide a description. Using well-known HTML methods in the description, you can add links to photographs. Placemarks are described in a KML file. The KML file, along with the images, is zipped up into a KMZ file to complete the job, and the KMZ file can be distributed to others who simply open it to view the locations and photographs.

 

Here is what Google Earth looks like for a placemark made by this script:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

For this project I exported selected photos from iPhoto to a trip images folder at full image size. I used photographs from a trip to Seattle and the Olympic Peninsula of Washington in 2007. I renamed the files with a prefix olympic-001, so that filenames became olympic-003-DSC_0056.jpg, olympic-003-DSC_0057.jpg, etc. for a given location.

 

In the python file, I typed the prefix (e.g., olympic-003) for each location, the latitude and longitude, and a description into a series of text strings, delimited by the vertical bar (|), which works well as a data delimiter. The script first parses this data into a Python dictionary of location codes and the images for each location.

 

Then a 'kmz' directory is created.

 

And into this 'kmz' directory, into a subdirectory called 'files', are written thumbnail images for the photographs, appending '-thumb' to the file basenames.

 

The KML file is written to contain the placemarks for the locations. The main trick here is to embed HTML tags for the description and thumbnail image links within a CDATA-bounded description section. The thumbnail image links have the form 'files/olympic-003-DSC_0057-thumb.jpg'.

 

Finally, an os.system() call is made to zip up the KML file and the thumbnail images in the files directory.

 

Here's the script, pykmz.py:

 

# Version 0.1

#

#  pykmz is copyright 2008-2009 Jeffrey G. Pittman.

#

#  This file is part of pykmz.

#

#  pykmz is free software: you can redistribute it and/or modify

#  it under the terms of the GNU General Public License as published by

#  the Free Software Foundation, either version 3 of the License, or

#  (at your option) any later version.

#

#  pykmz is distributed in the hope that it will be useful,

#  but WITHOUT ANY WARRANTY; without even the implied warranty of

#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the

#  GNU General Public License for more details.

#

#  You should have received a copy of the GNU General Public License

#  along with pykmz.  If not, see <http://www.gnu.org/licenses/>.

 

import glob

import os

from PIL import Image

 

codes_and_photo_fns = {}

for photo_fn in glob.glob('images/*.jpg'):

    basedir,basename = os.path.split(photo_fn)

    fn_root,fn_ext = os.path.splitext(basename)

    code = '-'.join(fn_root.split('-')[:-1])

    if code not in codes_and_photo_fns:

        codes_and_photo_fns[code] = [basename]

    else:

        codes_and_photo_fns[code].append(basename)

 

csv_data = """olympic-001 | Port of Seattle| Washington|  47 | 36 | 12 | 122 | 20 | 56 | Leaving the Port of Seattle

olympic-002| Lake Cresent | Washington |  48 | 3 | 8.5| 123 | 50 | 2.4 | At a roadside park

olympic-003| Mora Beach | Washington|   47 | 55 | 9 | 124 | 38 | 7 | On Mora Beach

olympic-004| Mora Beach Road | Washington|  47 | 55 | 19 | 124 | 38 | 21 | Eagles on Mora Road"""

 

kml = []

kml.append('<?xml version=\"1.0\" encoding=\"UTF-8\"?>')

kml.append('<kml xmlns=\"http://www.opengis.net/kml/2.2\">')

kml.append('<Document>')

 

for line in csv_data.split('\n'):

    line = line.strip()

 

    items = line.split('|')

 

    code = items[0].strip()

    locality = items[1].strip()

    area = items[2].strip()

    lat_deg = float(items[3])

    lat_min = float(items[4])

    lat_sec = float(items[5])

    lat = lat_deg + (lat_min/60.0) + (lat_sec/3600.0)

    lon_deg = float(items[6])

    lon_min = float(items[7])

    lon_sec = float(items[8])

    # longitude values are negative (West) for the U.S:

    lon = -(lon_deg + (lon_min/60.0) + (lon_sec/3600.0))

    description = items[9].strip()

 

    kml.append('  <Placemark>')

    kml.append('    <name>%s</name>' % code)

    kml.append('    <description><![CDATA[')

    kml.append('        <h1>%s</h1>' % locality)

    kml.append('        <p>%s</p>' % description)

    for photo_fn in codes_and_photo_fns[code]:

        basedir,basename = os.path.split(photo_fn)

        fn_root,fn_ext = os.path.splitext(basename)

        thumbnail_fn = fn_root + '_thumb' + fn_ext

        kml.append('        <p><img src=\"files/%s\" ></p>' % thumbnail_fn)

    kml.append('      ]]></description>')

    kml.append('    <Point>')

    kml.append('      <coordinates>%f,%f,0</coordinates>' % (lon, lat))

    kml.append('    </Point>')

    kml.append('  </Placemark>')

 

kml.append('</Document>')

kml.append('</kml>')

 

kml_string = '\n'.join(kml)

 

def check_dir(dest_dir):

    if os.path.isdir(dest_dir):

        print 'Aborting!'

        print '  (Directory ' + dest_dir + ' already exists.)'

        return False

    else:

        os.mkdir(dest_dir)

        return True

 

def make_thumbnails(size, kmz_dirname, image_dirs):

    print

    print '----------------'

    print 'Thumbnails...'

    print

    os.mkdir(os.path.join(kmz_dirname, 'files'))

    for image_dir in image_dirs:

        for fn in glob.glob(os.path.join(image_dir, '*.jpg')):

            if 'DSC' in fn:

                basedir,basename = os.path.split(fn)

                fn_root,fn_ext = os.path.splitext(basename)

                outfile = os.path.join(kmz_dirname, 'files', fn_root + '_thumb' + fn_ext)

                print 'Wrote:', outfile

                im = Image.open(fn)

                im.thumbnail(size, Image.ANTIALIAS)

                im.save(outfile)

 

kmz_dirname = 'kmz'

 

project = 'olympic'

 

thumbnail_size = (320, 320)

 

if check_dir(kmz_dirname):

    make_thumbnails(thumbnail_size, kmz_dirname, ['images',])

 

    kmz_file = os.path.join(kmz_dirname, '%s.kmz' % project)

    kml_file = os.path.join(kmz_dirname, '%s.kml' % project)

 

    print

    print '----------------'

    print 'KML file...'

    print

    fileObj = open(kml_file, 'w' )

    fileObj.write(kml_string)

    fileObj.close()

    print 'Wrote:', kml_file

 

    print

    print '----------------'

    print 'Zipping KMZ...'

    print

    os.chdir(kmz_dirname)

    os.system('zip -r %s.kmz *' % project)

    os.chdir('..')

    print

    print 'Zipped:', kmz_file

    print

 

UPDATE: Changed the name from kmzmaker to pykmz, because there is already a Java-based kmzmaker.

 

And here is the screen output from the script, for this example:

 

jeffs-macbook:pykmz jeff$ python pykmz.py

 

----------------

Thumbnails...

 

Wrote: kmz/files/olympic-001-DSC_0003_thumb.jpg

Wrote: kmz/files/olympic-002-DSC_0013_thumb.jpg

Wrote: kmz/files/olympic-003-DSC_0055_thumb.jpg

Wrote: kmz/files/olympic-003-DSC_0056_thumb.jpg

Wrote: kmz/files/olympic-003-DSC_0057_thumb.jpg

Wrote: kmz/files/olympic-003-DSC_0060_thumb.jpg

Wrote: kmz/files/olympic-004-DSC_0070_thumb.jpg

Wrote: kmz/files/olympic-004-DSC_0075_thumb.jpg

 

----------------

KML file...

 

Wrote: kmz/olympic.kml

 

----------------

Zipping KMZ...

 

  adding: files/ (stored 0%)

  adding: files/olympic-001-DSC_0003_thumb.jpg (deflated 1%)

  adding: files/olympic-002-DSC_0013_thumb.jpg (deflated 3%)

  adding: files/olympic-003-DSC_0055_thumb.jpg (deflated 1%)

  adding: files/olympic-003-DSC_0056_thumb.jpg (deflated 3%)

  adding: files/olympic-003-DSC_0057_thumb.jpg (deflated 6%)

  adding: files/olympic-003-DSC_0060_thumb.jpg (deflated 1%)

  adding: files/olympic-004-DSC_0070_thumb.jpg (deflated 5%)

  adding: files/olympic-004-DSC_0075_thumb.jpg (deflated 2%)

  adding: olympic.kml (deflated 73%)

 

Zipped: kmz/olympic.kmz