太久没来这里了,这两天又在之前批量调整照片日期的那段代码的基础上整了两个程序,拿来分享一下。

上周我买了个牛排,对就是the new iPad。哈哈,从此我可以在这上面得瑟我的照片啦~~ 啦呀啦,想一想这是一件多么美妙的事情啊!

题外话,iTunes很无耐,iTools很好用。当我满心欢喜地导入几千张以往的照片后,我崩溃了。

iOS的“照片”软件可以说做的很不错,除了不能建立子目录(可能是iOS限制的)以往,其他功能都还不错,尤其是“地图”功能,在Google Map中看到那一堆堆的“图钉”,那可是我曾走过的足迹啊!

【发现问题】
啊!不对,我没去过俄罗斯啊,也没到过钓鱼岛海域!那明明是昆明的石林啊,怎么回事?照片怎么出现在哪里?

我的照片中的GPS数据,都是我用软件(顺便广告一个GPicSync,类似的还有PhotoMapper(好大啊)和cPicture(2个文件,还是绿色的))把手机记录的GPS轨迹文件(.gpx文件)导入到JPG里的。(事先要给相机对表呦)还不明白?那你自己百度、Google吧。

是不是手机的GPS又因为没信号而漂移了? 不对啊,飘的也太多了,就没有一个准的!
是不是数据错了?在AcdSee里看看,没问题啊!
[img]http://dl.iteye.com/upload/attachment/0068/2585/6f7fdd8d-16e9-3e61-a823-99dcbfd12f1d.jpg[/img]

【分析问题】
我研究了一下Exif里的经纬度坐标,是个24长的byte数组,8个byte“度”,8个byte “分”,8个byte “秒”

8个byte当中,前4个代表分子(高位在后),后4个代表分母(高位在后)

如图,这是 纬度 29 33’ 43.3872”
[img]http://dl.iteye.com/upload/attachment/0068/2589/f1e1f025-8a77-33ca-88e0-7a6d25a011d2.jpg[/img]

我试过修改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;                }            }        }

然后,给ExifManager类增加GPS相关的属性

//北纬 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();            }        }

主程序只需递归遍历所有JPG文件,然后

                        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();                    }

注意,在读取经纬度时,我对原来的代码GetPropertyRational进行了重载。
因为这个函数原本的作用是读取8个byte的内容,而经纬度都有3*8个byte,用原来的函数只能读到“度”,要想读出“分”和“秒”就要向后便宜8个和16个byte

        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;            }        }

【结束语】
终于大功告成了。

[img]http://dl.iteye.com/upload/attachment/0068/2710/1bcc36fd-5639-3925-a93c-da61af7d2313.jpg[/img]

我在这个程序的About中写了如下文字:
“本软件用于将照片中的GPS坐标信息批量修改为苹果iOS系统所能识别的格式。

作为摄影爱好者,我喜欢在照片中保留拍摄地点的经纬度坐标,无论是使用内置GPS模块的相机,还是外置GPS附件,甚至是通过手机记录GPS轨迹,回家后再使用软件批量写入JPG文件(这需要相机的时钟要准确)。

然而我却发现这些照片在iPhone和iPad中所显示的位置与真实坐标相去甚远,并不是Google地图的混淆性偏移,那个也就几米、几十米,而这个偏差有几百几千甚至上万公里!

分析得知,虽然都是Exif2.21标准下的GPS信息,但苹果的格式存在某些特殊要求,这也许是苹果出于混淆精确度的考虑,苹果的最小精度为18米(赤道上)。

如果你准备把一组照片同步到iOS设备上,那么之前你可以使用本软件将它们的GPS格式改为iOS可以正确识别的。但这也带来了精度降低的问题,因此我建议你最好在PC上保留照片的转换前版本。

欢迎交流,新浪微博 @长江游泳鱼 http://weibo.com/10391867

最后提供下载(需要.Net Framework2.0及以上环境)
耐心一点,并没死。根据文件大小,大概1-3秒一张。虽然用了委托,但是进度条和Exif信息,到后面还是有些卡。谁能给点提示?

======================================================================

最后还要提一点,就是当我有多个相簿的时候,只有最后一个导入的相簿中的照片的GPS位置才能显示出来。也就是说一旦导入了新照片(不管是否含有GPS信息),那么以前的坐标位置就荡然无存了。

如果之前的问题可以用“非军事用途”解释,那么这一点应该是iOS的Bug了吧?

有线索的话,请跟帖告诉我好吗?

让照片在Apple(iphone / iPad)上显示在地图中正确的位置相关推荐

  1. 点击iPhone/iPad上的加密相册或保险箱提示“无法安装加密相册或保险箱,App Store已不提供此应用”解决方案

    点击iPhone/iPad上的加密相册.保险箱.加密相册Pro.保险箱Pro提示"无法安装加密相册或保险箱,App Store已不提供此应用"解决方案 1.本文适用条件: 1)任何 ...

  2. 如何在 iPhone 或 iPad 上查看 iCloud 钥匙串中的密码

    保存在 iPhone 上的 iCloud 钥匙串中的任何登录或付款详细信息会自动同步到您的 iPhone 和 iPad.如果您想查看您在 iCloud 钥匙串中保存的一个或所有密码,应该如何查看呢?请 ...

  3. imessage_如何在iPhone和iPad上的iMessage组中提及某人

    imessage Khamosh Pathak Khamosh Pathak Sometimes, it's difficult to get someone's attention in a lar ...

  4. 如何在iphone/ipad上安装低版本App

    当我们在iphone/ipad上安装新应用的时候,系统可能会提示此应用需要更高的系统版本,就比如说我现在是ios5,但是,此应用需要ios7以上的系统版本! 其实苹果是提供低版本下载的,只是系统不会提 ...

  5. 通过Mac远程调试iPhone/iPad上的网页(转)

    我们知道在 Mac/PC 上的浏览器都有 Web 检查器这类的工具(如最著名的 Firebug)对前端开发进行调试,而在 iPhone/iPad 由于限于屏幕的大小和触摸屏的使用习惯,直接对网页调试非 ...

  6. 怎样在ipad上显示pc_如何将iPad用作PC或Mac的辅助显示器

    怎样在ipad上显示pc Multiple monitors are awesome. With two screens side by side, you can more easily see a ...

  7. html5中插入视频无效原,来自wmv的h264剪辑在iPad上的HTML5视频中无效(黑屏)

    我需要在iPad上的视频标签中播放.wmv文件.使用Handbrake我已经在Chrome中播放了视频,所以我知道我的语法很好.我甚至还有另一个网站的mp4在iPad上播放没有任何问题. 这就是ffm ...

  8. 怎样在iPhone或iPad上的“查找我”中设置AirTag?

    激活新的AirTag到您的iPhone再简单不过了,对于想要跟踪钥匙和包等物品的人,您将希望尽快将AirTag附加到它们上.今天小编给大家带来具体的操作方法,需要的朋友欢迎参考操作! 激活AirTag ...

  9. 如何在iPhone或iPad上的“查找我”中设置AirTag?

    激活新的AirTag到您的iPhone再简单不过了,对于想要跟踪钥匙和包等物品的人,您将希望尽快将AirTag附加到它们上.今天小编给大家带来具体的操作方法,需要的朋友欢迎参考操作! 激活AirTag ...

最新文章

  1. 用jamon来监控你的sql执行效率
  2. How to correctly encode .mp4 files for streaming
  3. SAP Cloud Platform和S/4HANA的互联
  4. 英伟达_如何超越英伟达?
  5. Lintcode1 A+B Problem solution 题解
  6. MATLAB矩阵的算术运算
  7. javascript sort排序
  8. java系列9:对象数组
  9. 钩子教程 - 原理(一)
  10. Linux虚拟机添加新硬盘的全程图解
  11. mysql msi失败_MySQL .msi 安装失败改用.zip安装步骤
  12. 编译原理递归下降语法分析器C++实现
  13. fedora mysql添加密码_Fedora14下 mysql更改密码
  14. 虚拟机是ubuntu,windows映射盘符方式访问虚拟机
  15. IBM SPSS Modeler 【4】 神经网络模型的测试验证
  16. 超简单安装油猴(tampermonkey)脚本及使用教程
  17. Sharding Sphere实现数据“一键脱敏”
  18. 业务中台构建--业务驱动为核心的云原生体系建设思考
  19. 弘辽科技:直通车双重优化
  20. 《一本书读懂24种互联网思维》用户思维1

热门文章

  1. 全球最常用密码名单公布:“123456”排第二;谷歌神秘项目曝光:AI写代码,抢程序员饭碗;WSL GA发布|极客头条
  2. 推荐算法的介绍+简单例子
  3. 大数据安全分析与架构设计
  4. Chemex3.4 怎么修改资产设备那原始二维码,扫码可查看该资产详细信息
  5. 【软工】Alpha阶段测试报告
  6. 【简洁明了MySQL】MySQL基础操作之连接,创建和删除数据库
  7. ArcGIS基础:点要素分割线要素和提取线要素的交点
  8. 博客的HTML代码如何运用,html代码运用(五)--常用特效解析
  9. 操作系统面试问题汇总(超详细)
  10. mysql中的utf8与utf8mb4