`
TonyLian
  • 浏览: 402138 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

让照片在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;
                }
            }
        }



然后,给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;
            }
        }




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





我在这个程序的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了吧?

有线索的话,请跟帖告诉我好吗?
  • 大小: 546.7 KB
  • 大小: 163 KB
  • 大小: 142.3 KB
分享到:
评论
1 楼 beijiaoff 2013-12-30  
我正想说精度不可能只有18米,因为我照片地图可以很清楚的分辨道路两侧
然后我发现。。。啥啥啥,苹果地图不能把国外的地图缩放到最详细的两个层次?!
搜索了下,发现“有很多老外表示,来过一次中国后,回到自己国家地图也没法用了,必须刷机……”
这个太2b了,在中国用一下就把地图让高德接管了,把手机语言区域甚至挂vpn都解决不了。。我的手机还是德国买的呢!喵咪的!情何以堪。。

另外我有个问题请教,我特别喜欢ios7的图库对图片的归类整理方式,有时间地理位置的标题很方便,我用itools往相机胶卷里导入,结果出现了极少的几张照片(这是以前ipad拍的)会生成地理位置索引,而iphone拍的照片(确定有地理信息)却没有生成地理位置索引,很奇怪,这个问题怎么解决呢?

不能用itunes的同步,同步照片会把同步的照片按照同步的那天时间放在一块,而不是拍摄时间。而且同步的也没有地理位置信息。

相关推荐

    (0009)-iOS/iPhone/iPAD/iPod源代码-地图(Map)-iCodeMap

    在iOS开发中,地图(Map)是一个非常重要的组件,特别是在iPhone、iPad或iPod的应用程序中。本资源"(0009)-iOS/iPhone/iPAD/iPod源代码-地图(Map)-iCodeMap"提供了一个关于自定义地图功能的示例,主要涉及如何...

    (0098)-iOS/iPhone/iPAD/iPod源代码-地图(Map)-Customized MapKit

    在本主题"(0098)-iOS/iPhone/iPad/iPod源代码-地图(Map)-Customized MapKit"中,我们将探讨如何自定义地图,包括替换系统默认地图、添加个性化地图标注等技术。 一、自定义地图 自定义地图的核心是替换掉...

    (0051)-iOS/iPhone/iPAD/iPod源代码-地图(Map)-Customized Callout MKAnnotatio

    在iOS开发中,地图应用是常见且重要的功能之一,尤其是对于iPhone、iPad和iPod Touch等设备。本项目“(0051)-iOS/iPhone/iPAD/iPod源代码-地图(Map)-Customized Callout MKAnnotation”专注于自定义地图标注...

    Head First iPhone & iPad Development

    7. **地图和定位服务**:利用MapKit和Core Location框架,开发者可以集成地图和定位功能,书中会讲述如何在应用中显示地图、获取用户位置及导航。 8. **动画和性能优化**:iOS开发中,用户体验至关重要。书中会教授...

    iphone开发地图调用

    在iOS应用开发中,苹果提供了强大的地图服务,使得开发者能够轻松地在iPhone、iPad等设备上集成地图功能。本文将详细讲解如何在iPhone开发中调用地图,主要围绕标题“iPhone开发地图调用”展开,结合描述中的“完整...

    MacOS应用程序模拟你的iOS _ ipad或iphone模拟器设备位置支持WatchOS和TvOS

    标题中的“MacOS应用程序模拟你的iOS _ ipad或iphone模拟器设备位置支持WatchOS和TvOS”揭示了这个软件工具的主要功能,它允许开发者在Mac上模拟iOS设备(包括iPad和iPhone)的位置,甚至扩展到Apple WatchOS和TvOS...

    iPhone & iPad高级编程.rar

    《iPhone & iPad高级编程》是针对苹果iOS平台深入学习的一份综合资源包,主要涵盖了iPhone与iPad应用开发的高级技术和实践。这份压缩包文件包含了丰富的教学资料,旨在帮助开发者提升在iOS平台上的编程技能,从而...

    百度地图ipa

    开发者可以通过调用这些API,实现在自己的应用中显示地图、搜索地点、导航等功能,提升用户体验。 首先,集成百度地图API需要注册并获取API密钥(API Key)。开发者需要在百度地图开放平台创建一个项目,然后生成这...

    iPhone4与iPad开发基础教程 英文版

    - **位置服务与地图集成**:探索如何在应用中集成定位服务和地图显示功能。 #### 七、总结 本书不仅是一本优秀的iOS开发入门指南,更是引领读者进入移动应用开发领域的宝贵资源。通过本书的学习,你可以快速掌握...

    iPhone与iPad开发实战——精通iOS开发2.rar

    《iPhone与iPad开发实战——精通iOS开发》是一本深度探讨苹果移动设备应用开发的专业书籍,主要针对iOS操作系统,包括iPhone和iPad平台。本书旨在帮助开发者深入理解iOS开发环境,掌握Objective-C或Swift编程语言,...

    ipad ipad-mini中文使用说明书

    - **在iPad上查看此使用手册**:通过设置中的帮助文档查看详细使用指南。 #### 第3章:基本功能 - **使用应用程序**:介绍如何下载、安装及卸载应用程序。 - **自定义iPad**:包括更改壁纸、调整屏幕亮度等个性化...

    Find my iPhone定位的七种功能

    6. **地图定位**:"Find my iPhone"在地图上实时显示设备的位置,直观地帮助你找到设备,无论是被遗忘在家中的某个角落还是在外出时丢失。 7. **多设备支持**:这项服务不仅适用于iPhone,还包括iPad和iPod touch,...

    ios源码之点击地图上的指针弹出窗口(里面显示该地点的相关信息)Demo.rar

    5. **UIPopoverController或UIAlertController**:在iOS中,弹出窗口可以使用UIPopoverController(iPad)或UIAlertController(iPhone和iPad,iOS8及以上)。这个Demo可能根据设备类型和iOS版本选择合适的组件来...

    iPhone与iPad开发实战——精通iOS开发

    9. **MapKit与Core Location**:通过MapKit可以集成地图功能,而Core Location则用于获取设备位置信息,二者结合可以实现定位和导航功能。 10. **In-App Purchase**:学习如何集成内购服务,实现应用内的付费购买...

    swift版仿映客中间凸出tabBar,上下滑动时隐藏或显示导航栏及标签栏

    5. **布局适配**: 考虑到不同屏幕尺寸和设备方向,我们需要确保在横屏和竖屏模式下,以及在不同尺寸的iPhone和iPad上,UI布局都能正确显示。 在"EntireCustomTabBar"这个文件包中,可能包含了实现这一功能的源代码...

    ipad-note记事本.zip

    苹果公司的iPad内置了一个名为“Notes”的原生应用,它允许用户创建、编辑和管理文本、图片、手写笔记,甚至可以添加待办事项和地图位置。这个应用以其简洁的界面和强大的功能深受用户喜爱。在iPad上,你可以通过...

    斯坦福大学iPad&iPhone;开发教程2011秋

    该教程旨在帮助学生掌握苹果平台上的软件开发技能,特别是针对iPad和iPhone的应用设计与实现。本教程涵盖了Objective-C编程语言、模型-视图-控制器(MVC)架构、核心数据(Core Data)、持久化(Persistence)、核心位置...

    Find-My-iPhone-to-web:使用 Apple 的位置跟踪来确定我的位置,并将其添加到网页中

    标题 "Find-My-iPhone-to-web" 涉及到的技术是使用 Apple 的“查找我的 iPhone”功能结合 PHP 和 jQuery,将设备的位置信息实时显示在网页上。这是一项结合了移动设备定位服务与 web 技术的应用,允许用户通过网络...

Global site tag (gtag.js) - Google Analytics