所以本篇僅說明如何利用 .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 載入一張圖檔, 並取得該圖片的屬性清單, 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | //載入圖片 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) { //分別處理各個屬性 } } } |
雖然程式中取的範圍是 0x0000 至 0x001e, 不過實際上我們僅會用到 0x0000 至 0x0006.
接下來我們開始處理每個 case:
- GPSVersionID (0x0000): 1234567
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: 12345
case
0x0001:
//透過BitConverter, 將Value轉成Char('N' / 'S')
//此值在後續的Latitude計算上會用到
chrGPSLatitudeRef = BitConverter.ToChar(objItem.Value, 0);
break
;
- GPSLatitude (0x0002):
從表格中可以得知, 該屬性值是 rational64u[3], 但其 Value 會是 byte[24].
例如:計算方式為: 每 8 個 byte 為一組, 每一組的前 4bytes 轉 uint, 之後除以後 4bytes 轉成的 uint.12345byte
[] {
37, 0, 0, 0, 1, 0, 0, 0
, 44, 0, 0, 0, 1, 0, 0, 0
, 51, 29, 0, 0, 5, 1, 0, 0
}
經此計算後, 這三組將依序為 degrees, minutes, seconds.- 算出緯度值: 12345678910111213141516
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 123
//只要轉換一下輸出的格式
string
strLatitude=
string
.Format(
"{0:#} deg {1:#}' {2:#.00}\" {3}"
,
d, m, s, chrGPSLatitudeRef);
- 算出緯度值:
- GPSLongitudeRef (0x0003): 可參考 GPSLatitudeRef 的作法 ('E' = East, 'W' = West) 12345
case
0x0003:
//透過BitConverter, 將Value轉成Char('E' / 'W')
//此值在後續的Longitude計算上會用到
chrGPSLongitudeRef = BitConverter.ToChar(objItem.Value, 0);
break
;
- GPSLongitude (0x0004): 可參考 GPSLatitude 的計算方式 同樣地, 如果要輸出經度的表示式 (97 deg 15' 56.72" W), 只要調輸出的格式:12345678910111213141516
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
;
12Console.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. 123
case
0x0005:
string
strAltitude = BitConverter.ToBoolean(objItem.Value, 0) ?
"0"
:
"1"
;
break
;
- GPSAltitude (0x0006): 這個屬性值是 byte[8], 計算方式與經緯度相同, 取前 4bytes 轉成 uint, 再除以後 4bytes 轉成的 uint 值. 12345678
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)
張貼留言