มกราคม 2009
วัตถุประสงค์
บทแนะนํานี้จะสอนวิธีใช้รูปภาพที่ติดแท็กทางภูมิศาสตร์เพื่อสร้าง KML PhotoOverlays
แม้ว่าโค้ดตัวอย่างจะเขียนลงใน Python แต่ไลบรารีอื่นๆ ที่คล้ายกันก็มีให้บริการในภาษาโปรแกรมอื่นๆ ด้วย ดังนั้นจึงไม่น่าจะจําเป็นต้องแปลรหัสนี้เป็นภาษาอื่น โค้ดในบทความนี้ใช้ไลบรารี Python แบบโอเพนซอร์ส EXIF.py
บทนำ
กล้องดิจิทัลเป็นสิ่งที่ยอดเยี่ยมมาก ผู้ใช้หลายคนไม่รับรู้เรื่องนี้ แต่ทําได้มากกว่าแค่ถ่ายภาพและวิดีโอ พวกเขายังติดแท็กวิดีโอและรูปภาพเหล่านั้นด้วยข้อมูลเมตาเกี่ยวกับกล้องและการตั้งค่า ในช่วง 2 - 3 ปีที่ผ่านมา ผู้คนได้พบวิธีเพิ่มข้อมูลทางภูมิศาสตร์ลงในข้อมูลนั้นโดยฝังโดยผู้ผลิตกล้อง เช่น กล้องเปอร์โตริโกและ Nikon หรือผ่านอุปกรณ์อย่างเช่นตัวบันทึก GPS และ EyeFi Explore โทรศัพท์กล้องถ่ายรูป เช่น iPhone และโทรศัพท์ที่ใช้ระบบปฏิบัติการ Android เช่น G1 ของ T-Mobile จะฝังข้อมูลดังกล่าวโดยอัตโนมัติ เว็บไซต์อัปโหลดรูปภาพบางแห่ง เช่น Panoramio, Picasa Web Albums และ Flickr จะแยกวิเคราะห์ข้อมูล GPS โดยอัตโนมัติและใช้เพื่อติดป้ายสถานที่ให้กับรูปภาพ แล้วคุณจะได้รับข้อมูลนั้นคืนในฟีด แต่จะสนุกตรงไหน บทความนี้จะสํารวจวิธีเข้าถึงข้อมูลดังกล่าวด้วยตัวเอง
ส่วนหัว Exif
วิธีที่ใช้กันโดยทั่วไปเพื่อฝังข้อมูลไว้ในไฟล์ภาพคือการใช้รูปแบบไฟล์ภาพการแลกเปลี่ยนหรือ EXIF ข้อมูลจะจัดเก็บไว้ในรูปแบบไบนารีในส่วนหัว EXIF ด้วยวิธีมาตรฐาน หากทราบข้อมูลจําเพาะของส่วนหัว EXIF คุณก็แยกวิเคราะห์ด้วยตัวเองได้ โชคดีที่มีคนทํางานหนักและเขียนโมดูล Python ให้คุณแล้ว ไลบรารีแบบโอเพนซอร์ส EXIF.py เป็นเครื่องมือที่ยอดเยี่ยมสําหรับอ่านส่วนหัวของไฟล์ JPEG
รหัส
โค้ดตัวอย่างสําหรับบทความนี้อยู่ในไฟล์นี้: exif2 KML.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
มีภาพรวมที่ดีในการใช้งาน