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

Mano Marks, צוות Google Geo APIs
ינואר 2009

מטרה

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

מבוא

מצלמות דיגיטליות הן דבר די מדהים. משתמשים רבים לא מודעים לכך, אבל הם מבצעים הרבה יותר מצילום תמונות וסרטונים. הם גם מתייגים את הסרטונים והתמונות האלה באמצעות מטא-נתונים על המצלמה ועל ההגדרות שלה. בשנים האחרונות אנשים גילו דרכים להוסיף נתונים גיאוגרפיים למידע שמוטמע על ידי יצרני המצלמות, כמו מצלמות Ricoh ו-Nikon מסוימות, או מכשירים כמו יומני GPS וסיור ב-EFiFi. נתונים כאלה של מצלמות כמו iPhone וטלפונים שמשתמשים במערכת ההפעלה Android, כמו G1 של T-Mobile, מטמיעים את הנתונים האלה באופן אוטומטי. חלק מהאתרים שמעלים תמונות, כמו Panoramio , Google Photos ו-Netflix, ינתחו נתוני 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, אובייקטים לאחסון המונה והמכנה של הערכים. כך מתקבל יחס מדויק במקום להסתמך על מספר נקודה צפה (floating-point). אבל 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 כולל סקירה כללית טובה על השימוש בהם.