所以本篇僅說明如何利用 .Net 本身的 Library 從圖檔中取出這些資訊.
不過, 若圖檔本身未包裝這些資訊進去, 程式本身亦不可能讀到.
在說明如何取得這些資訊前, 請記得參考以下資訊:
- EXIF: http://zh.wikipedia.org/w/index.php?title=EXIF&variant=zh-tw
- EXIF Tags: http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html
- GPS Tags: http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/GPS.html
- 此範例參考的圖片: http://www.flickr.com/photos/inghramjp/3685983913/meta
選用此圖片的原因, 是因為在最下方的 GPS 資訊有包括緯度 (Latitude), 經度 (Longitude), 高度 (GPSAltitude), 版本 (GPSVersion).
Tag ID | Tag Name | Writable | Values / Notes |
---|---|---|---|
0x0000 | GPSVersionID | int8u[4]: | |
0x0001 | GPSLatitudeRef | string[2] | 'N' = North 'S' = South |
0x0002 | GPSLatitude | rational64u[3] | |
0x0003 | GPSLongitudeRef | string[2] | 'E' = East 'W' = West |
0x0004 | GPSLongitude | rational64u[3] | |
0x0005 | GPSAltitudeRef | int8u | 0 = Above Sea Level 1 = Below Sea Level |
0x0006 | GPSAltitude | rational64u |
開發工具: Vistual Studio 2008 + .Net Framework 3.5
首先, 先利用 Image 這個 Class 載入一張圖檔, 並取得該圖片的屬性清單, 如下:
//載入圖片 Image objImage = Image.FromFile(@"c:\3685983913_f633a22195_o.jpg"); //取得所有的屬性(以PropertyId做排序) var propertyItems = objImage.PropertyItems.OrderBy(x => x.Id); //暫訂緯度為N(北緯) char chrGPSLatitudeRef = 'N'; //暫訂經度為E(東經) char chrGPSLongitudeRef = 'E'; foreach (PropertyItem objItem in propertyItems) { //只取Id範圍為0x0000到0x001e if (objItem.Id >= 0x0000 && objItem.Id <= 0x001e) { switch (objItem.Id) { //分別處理各個屬性 } } }上述的 Id 部分請參考上表中的 Tag ID.
雖然程式中取的範圍是 0x0000 至 0x001e, 不過實際上我們僅會用到 0x0000 至 0x0006.
接下來我們開始處理每個 case:
- GPSVersionID (0x0000):
case 0x0000: //將byte[]中的元素都轉成string var query = from tmpb in objItem.Value select tmpb.ToString(); //將各個元素以'.'串接 string sreVersion = string.Join(".", query.ToArray()); break;
- GPSLatitudeRef (0x0001): 'N' = North, 'S' = South:
case 0x0001: //透過BitConverter, 將Value轉成Char('N' / 'S') //此值在後續的Latitude計算上會用到 chrGPSLatitudeRef = BitConverter.ToChar(objItem.Value, 0); break;
- GPSLatitude (0x0002):
從表格中可以得知, 該屬性值是 rational64u[3], 但其 Value 會是 byte[24].
例如:byte[] { 37, 0, 0, 0, 1, 0, 0, 0 , 44, 0, 0, 0, 1, 0, 0, 0 , 51, 29, 0, 0, 5, 1, 0, 0 }
計算方式為: 每 8 個 byte 為一組, 每一組的前 4bytes 轉 uint, 之後除以後 4bytes 轉成的 uint.
經此計算後, 這三組將依序為 degrees, minutes, seconds.- 算出緯度值:
case 0x0002: if (objItem.Value.Length == 24) { //degrees(將byte[0]~byte[3]轉成uint, 除以byte[4]~byte[7]轉成的uint) double d = BitConverter.ToUInt32(objItem.Value, 0) * 1.0d / BitConverter.ToUInt32(objItem.Value, 4); //minutes(將byte[8]~byte[11]轉成uint, 除以byte[12]~byte[15]轉成的uint) double m = BitConverter.ToUInt32(objItem.Value, 8) * 1.0d / BitConverter.ToUInt32(objItem.Value, 12); //seconds(將byte[16]~byte[19]轉成uint, 除以byte[20]~byte[23]轉成的uint) double s = BitConverter.ToUInt32(objItem.Value, 16) * 1.0d / BitConverter.ToUInt32(objItem.Value, 20); //計算緯度的數值, 如果是南緯, 要乘上(-1) double dblGPSLatitude = (((s/60 + m)/60)+d)*(chrGPSLatitudeRef.Equals('N') ? 1 : -1); } break;
- 呈現緯度的表示式: 例如, 37 deg 44' 28.64" N
//只要轉換一下輸出的格式 string strLatitude=string.Format("{0:#} deg {1:#}' {2:#.00}\" {3}", d, m, s, chrGPSLatitudeRef);
- 算出緯度值:
- GPSLongitudeRef (0x0003): 可參考 GPSLatitudeRef 的作法 ('E' = East, 'W' = West)
case 0x0003: //透過BitConverter, 將Value轉成Char('E' / 'W') //此值在後續的Longitude計算上會用到 chrGPSLongitudeRef = BitConverter.ToChar(objItem.Value, 0); break;
- GPSLongitude (0x0004): 可參考 GPSLatitude 的計算方式
case 0x0004: if (objItem.Value.Length == 24) { //degrees(將byte[0]~byte[3]轉成uint, 除以byte[4]~byte[7]轉成的uint) double d = BitConverter.ToUInt32(objItem.Value, 0) * 1.0d / BitConverter.ToUInt32(objItem.Value, 4); //minutes(將byte[8]~byte[11]轉成uint, 除以byte[12]~byte[15]轉成的uint) double m = BitConverter.ToUInt32(objItem.Value, 8) * 1.0d / BitConverter.ToUInt32(objItem.Value, 12); //seconds(將byte[16]~byte[19]轉成uint, 除以byte[20]~byte[23]轉成的uint) double s = BitConverter.ToUInt32(objItem.Value, 16) * 1.0d / BitConverter.ToUInt32(objItem.Value, 20); //計算緯度的數值, 如果是西經, 要乘上(-1) double dblGPSLongitude = (((s/60 + m)/60)+d)*(chrGPSLongitudeRef.Equals('E') ? 1 : -1); } break;
同樣地, 如果要輸出經度的表示式 (97 deg 15' 56.72" W), 只要調輸出的格式:Console.WriteLine("{0:#} deg {1:#}' {2:#.00}\" {3}", d, m, s, chrGPSLongitudeRef);
- GPSAltitudeRef (0x0005): 因為該屬性值是 0/1 (0 = Above Sea Level, 1 = Below Sea Level), 所以透過 BitConverter 轉成 Boolean.
case 0x0005: string strAltitude = BitConverter.ToBoolean(objItem.Value, 0) ? "0" : "1"; break;
- GPSAltitude (0x0006): 這個屬性值是 byte[8], 計算方式與經緯度相同, 取前 4bytes 轉成 uint, 再除以後 4bytes 轉成的 uint 值.
case 0x0006: if (objItem.Value.Length == 8) { //將byte[0]~byte[3]轉成uint, 除以byte[4]~byte[7]轉成的uint double dblAltitude = BitConverter.ToUInt32(objItem.Value, 0) * 1.0d / BitConverter.ToUInt32(objItem.Value, 4); } break;
將此值帶入 http://maps.google.com/?q=37.741289,-97.265755&spn=0.05,0.05&t=h&om=1&hl=zh-tw, 便可得到該圖片在 Map 中的定位.
若有人想開發類似的 Map 應用, 也可在使用者上傳圖檔後, 透過此方式將該圖檔的 GPS 圖檔存入至資料庫, 後續再取出這些資訊做加值應用.
4 則留言:
您好,我想請教一下一些有關這個程式碼的問題QQ"
我引用了您的程式碼,可是還是跑不出來GPS的訊息耶ˊˋ"
是我哪裡出錯了嗎?
他的import要import哪些東西呢
~"~試了很多次都還是不行...
Hi,
You need to add a reference "System.Drawing".
and using below namespace:
using System.Drawing;
using System.Drawing.Imaging;
請問 var propertyItems = objImage.PropertyItems.OrderBy(x => x.Id);
這行程式是在顯示所有屬性嗎?
還是只是宣告?
var propertyItems = objImage.PropertyItems.OrderBy(x => x.Id);
這段是將 Image 的屬性(Property) 簡單地用 id 做一個排序.
你也可以直接處理, 將 foreach 改成如下的code.
foreach (PropertyItem objItem in objImage.PropertyItems)
張貼留言