تحويل الصور الموضوع عليها علامات جغرافية إلى KML PhotoOverlays

Mano Marks، فريق Google Geo APIs
كانون الثاني (يناير) 2009

الغرض

يعلّمك هذا البرنامج التعليمي كيفية استخدام الصور الموضوع عليها علامات جغرافية لإنشاء ملف KML PhotoOverlays. على الرغم من أن نموذج الشفرة مكتوب بلغة Python، إلا أن العديد من المكتبات المماثلة تستخدم لغات برمجة أخرى، لذا ليس من السهل ترجمة هذه الشفرة إلى لغة أخرى. تعتمد الشفرة في هذه المقالة على مكتبة Python مفتوحة المصدر، EXIF.py.

المقدمة

تُعد الكاميرات الرقمية أشياء رائعة إلى حد كبير. لا يدرك الكثير من المستخدمين ذلك، لكنهم يفعلون أكثر من مجرد التقاط صور وفيديو. ويضعون أيضًا علامة على مقاطع الفيديو والصور هذه مع بيانات وصفية حول الكاميرا وإعداداتها. وفي السنوات القليلة الماضية، عثر المستخدمون على طرق لإضافة البيانات الجغرافية إلى تلك المعلومات، إمّا مضمّنة بواسطة الشركات المصنّعة للكاميرات، مثل بعض كاميرات Ricoh وNikon، أو من خلال أجهزة مثل أجهزة تسجيل GPS وEyeFi Explore. يمكنك تضمين هذه البيانات تلقائيًا في هواتف الكاميرا، مثل iPhone والهواتف التي تستخدم نظام التشغيل Android، مثل G1 من T-Mobile. ستعمل بعض مواقع تحميل الصور، مثل Panoramio وألبومات الويب بيكاسا و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 نظرة عامة جيدة على كيفية استخدامها.