המרת תמונות עם תיוג גיאוגרפי ל-PhotoOverlays ב-KML

מנו מארקס, צוות Google Geo APIs
ינואר 2009

מטרה

במדריך הזה נלמד איך להשתמש בתמונות עם תיוג גיאוגרפי כדי ליצור קובץ KML PhotoOverlays. קוד הדוגמה נכתב ב-Python, אבל יש הרבה ספריות דומות בשפות תכנות אחרות, כך שלא אמורה להיות בעיה לתרגם את הקוד הזה לשפה אחרת. הקוד במאמר הזה מסתמך על ספריית Python בקוד פתוח, ‏ EXIF.py.

מבוא

מצלמות דיגיטליות הן דבר מדהים. הרבה משתמשים לא מודעים לכך, אבל הם עושים הרבה יותר מאשר לצלם תמונות וסרטונים. הם גם מתייגים את הסרטונים והתמונות האלה במטא-נתונים לגבי המצלמה וההגדרות שלה. בשנים האחרונות אנשים מצאו דרכים להוסיף למידע הזה נתונים גיאוגרפיים, או מוטמעים על ידי יצרני המצלמות, כמו במצלמות מסוימות של Ricoh ו-Nikon, או באמצעות מכשירים כמו מכשירי GPS לתיעוד ו-EyeFi Explore. בטלפונים עם מצלמה כמו אייפון ובטלפונים עם מערכת ההפעלה Android, כמו G1 של T-Mobile, הנתונים האלה מוטמעים באופן אוטומטי. אתרים מסוימים להעלאת תמונות, כמו Panoramio,‏ Google Photos ו-Flickr, ינתחו את נתוני ה-GPS באופן אוטומטי וישתמשו בהם כדי לתייג תמונה במיקום גיאוגרפי. אחרי זה תוכלו לקבל את הנתונים האלה בחזרה בפידים. אבל איפה הכיף בזה? במאמר הזה נסביר איך אפשר לגשת לנתונים האלה בעצמכם.

כותרות Exif

הדרך הנפוצה ביותר להטמעת נתונים בקובץ תמונה היא באמצעות פורמט קובץ תמונה ניתן להחלפה, או EXIF. הנתונים מאוחסנים בפורמט בינארי בכותרות EXIF בצורה סטנדרטית. אם אתם יודעים מה המפרט של כותרות EXIF, אתם יכולים לנתח אותן בעצמכם. למזלכם, מישהו כבר עשה את העבודה הקשה וכתב מודול Python בשבילכם. ספריית הקוד הפתוח EXIF.py היא כלי מצוין לקריאת הכותרות של קובצי JPEG.

הקוד

קוד לדוגמה למאמר הזה נמצא בקובץ הזה: exif2kml.py. אם אתם רוצים לדלג ישירות לשימוש בו, אתם יכולים להוריד את המודול הזה, וגם את EXIF.py, ולמקם אותם באותה ספרייה. מריצים את הפקודה python exif2kml.py foo.jpg ומחליפים את foo.jpg בנתיב לתמונה עם תיוג גיאוגרפי. המערכת תיצור קובץ בשם test.kml.

ניתוח כותרות ה-Exif

‫EXIF.py מספק ממשק פשוט לחילוץ כותרות Exif. פשוט מריצים את הפונקציה process_file() והיא תחזיר את הכותרות כאובייקט dict.

def GetHeaders(the_file):
  """Handles getting the Exif headers and returns them as a dict.

  Args:
    the_file: A file object

  Returns:
    a dict mapping keys corresponding to the Exif headers of a file.
  """

  data = EXIF.process_file(the_file, 'UNDEF', False, False, False)
  return data

אחרי שמקבלים את כותרות ה-Exif, צריך לחלץ את קואורדינטות ה-GPS. ב-EXIF.py, אלה נחשבים לאובייקטים של Ratio, אובייקטים לאחסון המונה והמכנה של הערכים. הפעולה הזו מגדירה יחס מדויק במקום להסתמך על מספר נקודה צפה. אבל ב-KML מצפים למספרים עשרוניים, לא ליחסים. לכן, אתם צריכים לחלץ כל אחת מהקואורדינטות ולהמיר את המונה והמכנה למספר נקודה צפה יחיד בפורמט מעלות עשרוניות:

def DmsToDecimal(degree_num, degree_den, minute_num, minute_den,
                 second_num, second_den):
  """Converts the Degree/Minute/Second formatted GPS data to decimal degrees.

  Args:
    degree_num: The numerator of the degree object.
    degree_den: The denominator of the degree object.
    minute_num: The numerator of the minute object.
    minute_den: The denominator of the minute object.
    second_num: The numerator of the second object.
    second_den: The denominator of the second object.

  Returns:
    A deciminal degree.
  """

  degree = float(degree_num)/float(degree_den)
  minute = float(minute_num)/float(minute_den)/60
  second = float(second_num)/float(second_den)/3600
  return degree + minute + second


def GetGps(data):
  """Parses out the GPS coordinates from the file.

  Args:
    data: A dict object representing the Exif headers of the photo.

  Returns:
    A tuple representing the latitude, longitude, and altitude of the photo.
  """

  lat_dms = data['GPS GPSLatitude'].values
  long_dms = data['GPS GPSLongitude'].values
  latitude = DmsToDecimal(lat_dms[0].num, lat_dms[0].den,
                          lat_dms[1].num, lat_dms[1].den,
                          lat_dms[2].num, lat_dms[2].den)
  longitude = DmsToDecimal(long_dms[0].num, long_dms[0].den,
                           long_dms[1].num, long_dms[1].den,
                           long_dms[2].num, long_dms[2].den)
  if data['GPS GPSLatitudeRef'].printable == 'S': latitude *= -1
  if data['GPS GPSLongitudeRef'].printable == 'W': longitude *= -1
  altitude = None

  try:
    alt = data['GPS GPSAltitude'].values[0]
    altitude = alt.num/alt.den
    if data['GPS GPSAltitudeRef'] == 1: altitude *= -1

  except KeyError:
    altitude = 0

  return latitude, longitude, altitude

אחרי שמקבלים את הקואורדינטות, קל ליצור PhotoOverlay פשוט לכל תמונה:

def CreatePhotoOverlay(kml_doc, file_name, the_file, file_iterator):
  """Creates a PhotoOverlay element in the kml_doc element.

  Args:
    kml_doc: An XML document object.
    file_name: The name of the file.
    the_file: The file object.
    file_iterator: The file iterator, used to create the id.

  Returns:
    An XML element representing the PhotoOverlay.
  """

  photo_id = 'photo%s' % file_iterator
  data = GetHeaders(the_file)
  coords = GetGps(data)

  po = kml_doc.createElement('PhotoOverlay')
  po.setAttribute('id', photo_id)
  name = kml_doc.createElement('name')
  name.appendChild(kml_doc.createTextNode(file_name))
  description = kml_doc.createElement('description')
  description.appendChild(kml_doc.createCDATASection('<a href="#%s">'
                                                     'Click here to fly into '
                                                     'photo</a>' % photo_id))
  po.appendChild(name)
  po.appendChild(description)

  icon = kml_doc.createElement('icon')
  href = kml_doc.createElement('href')
  href.appendChild(kml_doc.createTextNode(file_name))

  camera = kml_doc.createElement('Camera')
  longitude = kml_doc.createElement('longitude')
  latitude = kml_doc.createElement('latitude')
  altitude = kml_doc.createElement('altitude')
  tilt = kml_doc.createElement('tilt')

  # Determines the proportions of the image and uses them to set FOV.
  width = float(data['EXIF ExifImageWidth'].printable)
  length = float(data['EXIF ExifImageLength'].printable)
  lf = str(width/length * -20.0)
  rf = str(width/length * 20.0)

  longitude.appendChild(kml_doc.createTextNode(str(coords[1])))
  latitude.appendChild(kml_doc.createTextNode(str(coords[0])))
  altitude.appendChild(kml_doc.createTextNode('10'))
  tilt.appendChild(kml_doc.createTextNode('90'))
  camera.appendChild(longitude)
  camera.appendChild(latitude)
  camera.appendChild(altitude)
  camera.appendChild(tilt)

  icon.appendChild(href)

  viewvolume = kml_doc.createElement('ViewVolume')
  leftfov = kml_doc.createElement('leftFov')
  rightfov = kml_doc.createElement('rightFov')
  bottomfov = kml_doc.createElement('bottomFov')
  topfov = kml_doc.createElement('topFov')
  near = kml_doc.createElement('near')
  leftfov.appendChild(kml_doc.createTextNode(lf))
  rightfov.appendChild(kml_doc.createTextNode(rf))
  bottomfov.appendChild(kml_doc.createTextNode('-20'))
  topfov.appendChild(kml_doc.createTextNode('20'))
  near.appendChild(kml_doc.createTextNode('10'))
  viewvolume.appendChild(leftfov)
  viewvolume.appendChild(rightfov)
  viewvolume.appendChild(bottomfov)
  viewvolume.appendChild(topfov)
  viewvolume.appendChild(near)

  po.appendChild(camera)
  po.appendChild(icon)
  po.appendChild(viewvolume)
  point = kml_doc.createElement('point')
  coordinates = kml_doc.createElement('coordinates')
  coordinates.appendChild(kml_doc.createTextNode('%s,%s,%s' %(coords[1],
                                                              coords[0],
                                                              coords[2])))
  point.appendChild(coordinates)

  po.appendChild(point)

  document = kml_doc.getElementsByTagName('Document')[0]
  document.appendChild(po)

אפשר לראות שאנחנו משתמשים רק בשיטות סטנדרטיות של W3C DOM, כי הן זמינות ברוב שפות התכנות. כדי לראות איך הכול משתלב, אפשר להוריד את הקוד כאן.

בדוגמה הזו לא נעשה שימוש בכל היכולות של PhotoOverlays, שמאפשרות ליצור ניתוחים מעמיקים של תמונות ברזולוציה גבוהה. אבל הוא כן מדגים איך לתלות תמונה בסגנון של שלט חוצות מעל Google Earth. זו דוגמה לקובץ KML שנוצר באמצעות הקוד הזה:

<?xml version="1.0" encoding="utf-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
  <Document>
    <PhotoOverlay id="photo0">
      <name>
        1228258523134.jpg
      </name>
      <description>
<![CDATA[<a href="#photo0">Click here to fly into photo</a>]]>      </description>
      <Camera>
      	<longitude>
          -122.3902159196034
        </longitude>
        <latitude>
           37.78961266330473
        </latitude>
        <altitude>
          10
        </altitude>
        <tilt>
          90
        </tilt>
      </Camera>
      <Icon>
        <href>
          1228258523134.jpg
        </href>
      </Icon>
      <ViewVolume>
        <leftFov>
          -26.6666666667
        </leftFov>
        <rightFov>
          26.6666666667
        </rightFov>
        <bottomFov>
          -20
        </bottomFov>
        <topFov>
          20
        </topFov>
        <near>
          10
        </near>
      </ViewVolume>
      <Point>
        <coordinates>
          -122.3902159196034,37.78961266330473,0
        </coordinates>
      </Point>
    </PhotoOverlay>
  </Document>
</kml>

כך זה נראה ב-Google Earth:


אזהרות

התיוג הגיאוגרפי של תמונות נמצא עדיין בשלבי פיתוח ראשוניים.

כמה דברים שחשוב לדעת:

  • מכשירי GPS לא תמיד מדויקים ב-100%, במיוחד אלה שמגיעים במצלמות, ולכן כדאי לבדוק את המיקומים של התמונות.
  • במכשירים רבים, הגובה לא נמדד, והערך שמוגדר הוא 0. אם הגובה חשוב לכם, כדאי למצוא דרך אחרת לתעד את הנתונים האלה.
  • מיקום ה-GPS הוא המיקום של המצלמה, ולא של נושא התמונה. לכן בדוגמה הזו, רכיב המצלמה ממוקם במיקום ה-GPS, והתמונה עצמה ממוקמת במקום אחר.
  • נתוני ה-Exif לא כוללים מידע על הכיוון שאליו המצלמה מכוונת, ולכן יכול להיות שתצטרכו לשנות את PhotoOverlays. החדשות הטובות הן שבחלק מהמכשירים, כמו טלפונים שמבוססים על מערכת ההפעלה Android, אפשר לצלם נתונים כמו כיוון המצפן והטיה ישירות, אבל לא בכותרות Exif.

למרות כל זאת, זו עדיין דרך עוצמתית להצגת התמונות. אנחנו מקווים שבעתיד הקרוב נראה יותר ויותר תיוג גיאוגרפי מדויק של תמונות.

לאן כדאי ללכת מכאן

אחרי שמתחילים להשתמש בכותרות EXIF, כדאי לעיין במפרט EXIF. יש שם הרבה נתונים אחרים שאולי תרצו לתעד ולהציג בבלון תיאור. אפשר גם ליצור PhotoOverlays עשירים יותר באמצעות ImagePyramids. במאמר במדריך למפתחים בנושא PhotoOverlays יש סקירה כללית טובה של השימוש בהם.