让照片在Apple(iphone / iPad)上显示在地图中正确的位置
太久没来这里了,这两天又在之前批量调整照片日期的那段代码的基础上整了两个程序,拿来分享一下。
上周我买了个牛排,对就是the new iPad。哈哈,从此我可以在这上面得瑟我的照片啦~~ 啦呀啦,想一想这是一件多么美妙的事情啊!
题外话,iTunes很无耐,iTools很好用。当我满心欢喜地导入几千张以往的照片后,我崩溃了。
iOS的“照片”软件可以说做的很不错,除了不能建立子目录(可能是iOS限制的)以往,其他功能都还不错,尤其是“地图”功能,在Google Map中看到那一堆堆的“图钉”,那可是我曾走过的足迹啊!
【发现问题】
啊!不对,我没去过俄罗斯啊,也没到过钓鱼岛海域!那明明是昆明的石林啊,怎么回事?照片怎么出现在哪里?
我的照片中的GPS数据,都是我用软件(顺便广告一个GPicSync,类似的还有PhotoMapper(好大啊)和cPicture(2个文件,还是绿色的))把手机记录的GPS轨迹文件(.gpx文件)导入到JPG里的。(事先要给相机对表呦)还不明白?那你自己百度、Google吧。
是不是手机的GPS又因为没信号而漂移了? 不对啊,飘的也太多了,就没有一个准的!
是不是数据错了?在AcdSee里看看,没问题啊!
【分析问题】
我研究了一下Exif里的经纬度坐标,是个24长的byte数组,8个byte“度”,8个byte “分”,8个byte “秒”
8个byte当中,前4个代表分子(高位在后),后4个代表分母(高位在后)
如图,这是 纬度 29 33’ 43.3872”
我试过修改PGX文件,弄了一个整数的经纬度,然后导入JPG,在弄到pad上,它的位置就正常了。
我以为是我的写入软件不好,但是又找了2个同样功能的软件(就是上面说的那一大一小),问题依旧。
是我这种DIY式的带GPS信息的JPG的问题吗? 我的Android手机打开GPS,拍一张,导入iPad,哇咔咔,俄罗斯去啦!
于是,我又找了2张iPhone拍的照片,GPS数据和iPad拍照的有同样的规律:
1.没有“秒”信息(固定分子0,分母1),“秒”是靠“分”位的小数来表示的
2.“分”位的分母一律是100。两位小数精度
而通常的照片,那8个Byte来看,4个分子,4个分母,所以无限不循环是家常便饭了。
让我想不通的是,同样都是Exif 2.21格式,Apple怎么只认满足上面2点的特例的呢!?
唯一能让自己听着过的去的解释,就是为了降低精度,避免一些法律问题(怕你去炸大楼)
而Apple用的方法不像Google加入人为偏移量,而是最简单也最彻底的办法:缩小数据精度。这样一来,最小精度只有0.01分,就是0.6秒。在赤道上就是18米!
所以,坐标上记录的位置和真实位置,差个8、9米就很常见啦。我一路走,一路拍的照片,也变成三五成堆的啦。
【解决问题】
那么,打开一个已有GPS信息的JPG,从Exif中读取出经纬度,并按照苹果的格式重新写入,再做保存。这不就解决问题了吗,我的那上千张带GPS信息的照片,终于在iPad上出现在Google Map的正确位置了。(由于强制采用GCJ-02等加密算法而导致的几米、几十米的偏差,可在App Store中找“地图相册”来解决,这个App可以做修正)
在网上搜索来的“C#读取Exif源码”基础上,追加如下代码:
首先是“坐标”类
public struct Coordinates { private double degrees; public double Degrees { get { return degrees; } } private double minutes; public double Minutes { get { return minutes; } } private double seconds; public double Seconds { get { return seconds; } } public Coordinates(double degrees) : this(degrees, 0, 0) { } public Coordinates(double degrees, double minutes) : this(degrees, minutes, 0) { } public Coordinates(double degrees, double minutes, double seconds) { this.degrees = Math.Floor(degrees); minutes += (degrees - this.degrees) * 60; this.minutes = Math.Floor(minutes); this.seconds = seconds + (minutes - this.minutes) * 60; } public new string ToString() { string str = ""; try { str = this.degrees.ToString() + "," + this.minutes.ToString() + "' " + Math.Round(this.seconds, 2).ToString() + "" "; } catch { } return str; } public double Value { get { return this.degrees + this.minutes / 60 + this.seconds / 60 / 60; } } }
//北纬 or 南纬? public string GpsLatitudeRef { get { return this.GetPropertyString((int)TagNames.GpsLatitudeRef); } set { this.SetPropertyString((int)TagNames.GpsLatitudeRef, value); } }//纬度 public Coordinates GpsLatitude { get { double degrees = this.GetPropertyRational((int)TagNames.GpsLatitude).ToDouble(); double minutes = this.GetPropertyRational((int)TagNames.GpsLatitude, 8).ToDouble(); double seconds = this.GetPropertyRational((int)TagNames.GpsLatitude, 16).ToDouble(); minutes += 60 * (degrees - Math.Floor(degrees)); degrees = Math.Floor(degrees); seconds += 60 * (minutes - Math.Floor(minutes)); minutes = Math.Floor(minutes); return new Coordinates(degrees, minutes, seconds); } set { try { byte[] bytes = new byte[24]; for (int i = 0; i < 24; i++) { bytes[i] = 0; } bytes[0] = (byte)value.Degrees; bytes[4] = 1; int min = (int)(Math.Round(value.Minutes + value.Seconds / 60,2) * 100); if (min > 256) { bytes[9] = (byte)(int)Math.Floor(min / 256D); bytes[8] = (byte)(min - bytes[9] * 256); } else { bytes[8] = (byte)min; } bytes[12] = 100; bytes[20] = 1; this.SetProperty((int)TagNames.GpsLatitude, bytes, ExifDataTypes.UnsignedRational); } catch(Exception e) { string a = e.ToString(); } } }//经度就略了。。。//海拔 public double GpsAltitude { get { return this.GetPropertyRational((int)TagNames.GpsAltitude).ToDouble(); } }
ExifManager exif = new ExifManager(fi.FullName); string latRef = exif.GpsLatitudeRef; if (latRef.Length > 0) { txtFileName.Text = fi.FullName; txtExif.Text = exif.ToString(); this.Refresh(); if (this.backupToolStripMenuItem.Checked) { string backupPath = fi.DirectoryName + "\\backup"; if (!Directory.Exists(backupPath)) { Directory.CreateDirectory(backupPath); } fi.CopyTo(backupPath + "\" + fi.Name); } ExifManager.Coordinates x = exif.GpsLatitude; exif.GpsLatitude = new ExifManager.Coordinates(x.Value); ExifManager.Coordinates y = exif.GpsLongitude; exif.GpsLongitude = new ExifManager.Coordinates(y.Value); try { if (File.Exists(fi.DirectoryName + TEMP)) { File.Delete(fi.DirectoryName + TEMP); } exif.Save(fi.DirectoryName + TEMP); exif.Dispose(); fi.Delete(); File.Move(fi.DirectoryName + TEMP, fi.FullName); count++; //Thread.Sleep(10); } catch { MessageBox.Show("文件操作失败,请确保没有其他程序正在打开文件 " + fi.Name, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } exif.Dispose(); progressBar.Value++; this.Refresh(); }
public Rational GetPropertyRational(Int32 PID) { return GetPropertyRational(PID, 0); } public Rational GetPropertyRational(Int32 PID, Int16 disp) { if (IsPropertyDefined(PID)) { byte[] arr = new byte[8]; Array.Copy(this._Image.GetPropertyItem(PID).Value, disp, arr, 0, 8); return GetRational(arr); } else { Rational R; R.Numerator = 0; R.Denominator = 1; return R; } }