【问题标题】:MKMapView setRegion "snaps" to predefined zoom levels?MKMapView setRegion“捕捉”到预定义的缩放级别?
【发布时间】:2010-08-31 18:24:11
【问题描述】:

谁能确认 setRegion “捕捉”到预定义的缩放级别,以及这种行为是否符合设计(尽管未记录)或已知错误?具体来说,setRegion 似乎会捕捉到与用户双击地图时使用的缩放级别相对应的相同缩放级别。

我正在尝试恢复以前保存的区域,但如果保存的区域是通过捏拉缩放而不是双击缩放设置的,则此行为将导致无法恢复。

如果我在地图的 current 区域上调用 regionThatFits,对我来说,mapkit 方面出现问题的一个重要线索是。它应该返回相同的区域(因为它显然适合地图的框架),但它会返回对应于下一个更高的预定义缩放级别的区域。

setVisibleMapRect 的行为类似。

任何进一步的见解或信息将不胜感激。

我找到了这些相关的帖子,但没有提供解决方案或明确确认这实际上是一个 mapkit 错误:

MKMapView setRegion: odd behavior?

MKMapView show incorrectly saved region

编辑:

这是一个演示问题的示例。所有值都对我的地图视图的纵横比有效:

MKCoordinateRegion initialRegion;
initialRegion.center.latitude = 47.700200f;
initialRegion.center.longitude = -122.367109f;
initialRegion.span.latitudeDelta = 0.065189f;
initialRegion.span.longitudeDelta = 0.067318f;
[map setRegion:initialRegion animated:NO];
NSLog(@"DEBUG initialRegion:  %f  %f  %f  %f", initialRegion.center.latitude, initialRegion.center.longitude, initialRegion.span.latitudeDelta, initialRegion.span.longitudeDelta);
NSLog(@"DEBUG map.region:  %f  %f  %f  %f", map.region.center.latitude, map.region.center.longitude, map.region.span.latitudeDelta, map.region.span.longitudeDelta);

输出:

DEBUG initialRegion:  47.700199  -122.367111  0.065189  0.067318
DEBUG map.region:  47.700289  -122.367096  0.106287  0.109863

注意纬度/经度增量值的差异。地图的值几乎是我要求的两倍。较大的值对应于用户双击地图时使用的缩放级别之一。

【问题讨论】:

    标签: iphone mapkit mkmapview


    【解决方案1】:

    是的,它会捕捉到离散的水平。我已经做了很多实验,它似乎喜欢每像素 2.68220906e-6 经度的倍数。

    所以如果你的地图填满了屏幕的整个宽度,第一级跨越 0.0008583 度,那么你可以得到的下一个级别是两倍,0.001717,然后下一个是两倍,0.003433,等等。我不确定他们为什么选择按经度进行归一化,这意味着修复缩放级别取决于您正在查看的世界的哪个部分。

    我也花了很多时间试图理解这个数字 .68220906e-6 度的意义。它在赤道处大约为 30 厘米,这是有道理的,因为谷歌地图使用的高分辨率照片具有 30 厘米的分辨率,但我希望他们使用纬度而不是经度来确定缩放级别。这样一来,在最大缩放时,您始终可以使用卫星图像的原始分辨率,但谁知道呢,他们可能有一些聪明人的理由让它这样工作。

    在我的应用程序中,我需要显示一定范围的纬度。我将编写一些代码以尝试将地图缩放到尽可能接近该位置。有兴趣的可以联系我。

    【讨论】:

    • 不认为你有什么用这个?
    • 不,但也许新的 Apple Maps SDK 会解决我们所有的问题 :)
    • 也许吧。如果您的意思是“解决”必须为新 SDK 重写所有与 MapKit 相关的代码;)
    • 既然 MapKit 使用矢量,Apple 绝对没有理由预先定义缩放级别,但情况仍然如此。
    • 查看我的答案,了解这些数字的来源,以及为什么它们与经度保持一致。
    【解决方案2】:

    我找到了解决办法。

    如果接收到的捕捉缩放级别是,可以说比所需缩放级别大 1.2 倍: 使用此算法进行更正:
    假设:您希望将地图视图设置为从左到右准确显示“longitudinalMeters”
    1) 计算校正比例:
    计算你收到的纵向跨度与你得到的纵向跨度之间的关系。

        MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(center, 0, longitudinalMeters);
    
        MKCoordinateRegion regionFits = [mapView regionThatFits: region];
        double correctionFactor = regionFits.span.longitudeDelta / region.span.longitudeDelta;
    

    2) 创建转换并将其应用于地图

       CGAffineTransform mapTransform = CGAffineTransformMakeScale(correctionScale, correctionScale);       
       CGAffineTransform pinTransform = CGAffineTransformInvert(mapTransform);
       [mapView setTransform:mapTransform];
    

    3) 对地图图钉应用逆变换,使其保持原始大小

     [mapView setTransform:mapTransform];
     for (id<MKAnnotation> annotation in self.mapView.annotations)
     {
         [[self.mapView viewForAnnotation:annotation] setTransform:pinTransform];
     }
    

    【讨论】:

    • 这个问题是整个地图视图被缩放,所以(a)用户的当前位置蓝点被缩放,并且(b)谷歌徽标可以消失并且根据许可协议这需要可见。我也在拼命寻找一种以编程方式设置缩放级别以避免这种“捕捉”的方法。
    • 是的,我的蓝点也有同样的问题,它有时会缩放,但大多数时候不会。您可以绘制自己的用户位置。此外,可以手动放置 Google 徽标。这里有关于旋转地图视图的帖子,必须明确添加谷歌徽标。此外,我认为我的解决方案是唯一有效的解决方案,地图视图根本没有准确设置缩放比例的功能。
    • 我在这个问题上做了很多工作。在 iOS 6 之前,给出的解决方案有效。从 iOS 6 开始,注解的仿射变换会不时重置,看似随机。我最终不得不 KVO 观察添加到地图中的每个注释的仿射变换,然后在每次修改时将仿射变换重置为想要的。
    • 另外,我已经直接询问了 Apple,无法使用 Apple 提供给我们的当前 API (iOS 6) 准确设置所需区域。
    • @Fritzlab:感谢您提供 KVO 信息,我也这么认为,以拦截设置的变换。但是对于 iOS6,似乎所有东西都可以开箱即用,我可以准确地设置缩放级别,我不需要上面描述的变换方法。上面的 CorrectionFactor 接近 1:0,99996 例如,所以对我来说,自从 ios6 以来,它似乎已经解决了。 (不过还没有足够的测试)(ios6使用矢量图,所以苹果没有绑定googles预渲染jps)
    【解决方案3】:

    奇怪的行为似乎是由于当一个人请求一个特定的区域或视图大小时,对谷歌的实际 API 调用是用一个中心点和一个缩放级别调用的。例如:

    map.setCenter(new google.maps.LatLng(234.3453, 454.2345), 42);
    

    现在 Apple 可以请求适当的缩放级别,然后调整视图的大小以适应实际的区域请求,但他们似乎没有这样做。我正在地图上绘制公交路线,我的一条路线几乎不会触发更大的缩放级别,因此缩放太小(缩放不足)并且看起来丑陋和破碎。

    @pseudopeach,请向我介绍您尝试解决此问题的进展情况。如果可以检测到缩放级别的边界,则可以故意缩小区域请求以避免缩放不足。由于您对此感兴趣,因此我有兴趣在自己尝试之前查看您的代码。

    博客 Backspace Prolog 的作者编写了一个有趣的类别,通过模拟他们的 setCenter(centerPoint,ZoomLevel) 调用签名来实现对 Google Maps API 的直接操作。你可以找到它here。我还没有花时间,但是可以对数学进行逆向工程以产生一种计算给定区域或 MapRect 的缩放级别的方法。根据它在缩放级别范围内的距离 - 即它超过触发较低缩放级别的阈值的距离 - 它可以通过请求不足来决定是转到较低级别还是保持较高级别。

    这显然是一个需要修复的行为错误,以便可以以更精细的方式使用 MKMapView。

    【讨论】:

      【解决方案4】:

      这是一个老问题,但我最近详细调查了谷歌地图,并可以分享一些见解。我不知道这是否也适用于当前的 Apple 地图。

      分辨率与预定义缩放级别对齐的原因是,从 Google 服务器获取的原始地图是使用这些缩放级别绘制的。这些地图上特征的大小是在一定的分辨率下绘制的。例如,这些地图上道路的宽度(以像素为单位)始终相同。在更高分辨率的地图上,会绘制更多的次要道路,但它们的宽度始终相同。分辨率会捕捉到预定义的级别,以确保始终以相同的大小描绘这些特征。也就是说,它不是一个错误,而是一个特性。

      由于地图的Mercator projection,这些预定义的分辨率随纬度而变化。墨卡托投影很容易使用,因为纬线被描绘成直线和水平,而经线被描绘成直线和垂直。但是对于墨卡托投影,地图顶部的分辨率略高于底部(在北半球)。这会对在北部和南部边缘将地图拟合在一起产生影响。

      也就是说,当您从赤道出发向北行驶时,您经过的墨卡托地图的分辨率会逐渐增加。经线保持垂直,因此经度跨度保持不变。但是分辨率增加了,因此纬度跨度减小了。尽管如此,在所有这些地图上,道路的像素宽度都是相同的,文本以相同的字体大小描绘,等等。

      Google 使用墨卡托投影,其中赤道周长为 256 像素,缩放级别为 0。每个下一个缩放级别都会使该数量翻倍。也就是说,在缩放级别 1 时,赤道是 512 像素长,在缩放级别 2 时,赤道是 1024 像素长,等等。他们使用的地球模型是 FAI 地球仪,半径正好为 6371 公里,或周长为 40030公里。 因此,在赤道缩放级别 0 的分辨率为 156.37 公里/像素,在缩放级别 1 为 78.19 公里/像素,依此类推。这些分辨率会随着地球上其他任何地方的纬度余弦而变化。

      【讨论】:

      • 如果将固定缩放级别与纬度挂钩,这将是有意义的,因为地球上任何地方的每个纬度度约为 68 英里。但在 MKMapkit 中却不是这样,它与经度挂钩,这没有任何意义,而且几乎可以肯定是一个错误。让您的地图捕捉到固定的经度跨度可以让您永远无法获得一致的缩放级别。
      • @pseudopeach 我不确定“与纬度挂钩的缩放级别”是什么意思。缩放级别就是它们。属于固定缩放级别的分辨率随纬度而变化,因为墨卡托投影的分辨率随纬度而变化。地球上的每个纬度都是恒定的(大约每分钟 1 海里),但它不在墨卡托投影上。在墨卡托投影上,经度是恒定的。
      • 我的意思是,在赤道,地图的最大分辨率为 30 cm/px,而远离赤道的最大分辨率为 50 cm/px。最大缩放级别(和所有其他级别)将始终跨越相同的经度范围,这意味着当您偏离赤道时它实际上是“缩小”。相反,如果他们将离散的缩放级别与特定的纬度跨度联系起来,那么以每像素厘米计的分辨率将在任何地方都是恒定的。 (我确定这就是他们的本意,但有人在代码中打错了字)
      • @pseudopeach 离散缩放级别与地图的原始分辨率相关联。正如我试图解释的那样,在墨卡托投影中,地图的北端(比如:30 px/km)比南端(比如:25 px/km)具有更高的分辨率。因此,其以北的地图在南端必须有 30 px/km,否则您将在两张地图相交的边缘看到不连续性。然后,第二张地图的北边必然有 35 像素/公里,等等。它绝对不是代码中的一种类型。阅读墨卡托投影,你就会明白。
      【解决方案5】:

      MKCoordinateRegion region;

      region.center.latitude = latitude;
      region.center.longitude = longitude;
      region.span.latitudeDelta = 5.0;
      region.span.longitudeDelta = 5.0;
      [mapView setRegion:region animated:YES];
      

      【讨论】:

        【解决方案6】:

        正如您所描述的那样,我没有问题且没有差异地恢复了该区域。如果没有一些代码可以查看,真的不可能说出你的情况有什么特别的错误,但是这对我有用:

        将中心值和跨度值保存在某处。当您恢复它们时,专门设置中心和跨度。

        恢复应该是这样的:

        MKCoordinateRegion initialRegion;
        initialRegion.center.latitude = Value you've stored
        initialRegion.center.longitude = Value you've stored 
        initialRegion.span.latitudeDelta = Value you've stored
        initialRegion.span.longitudeDelta = Value you've stored
        [self.mapView setRegion:initialRegion animated:NO];
        

        还请记住,此方法在 4.0 中可用:`mapRectThatFits:edgePadding: MapRectThatFits 有助于添加合理的边框,以确保边缘上的地图注释不会被遮挡并且您尝试显示的矩形完全可见的。如果您想控制边框,请使用可让您访问 set edgePadding 的调用。

        【讨论】:

        • 谢谢。我已经用从您的示例中借用的一个特定示例更新了我的原始帖子,该示例演示了该问题。
        • 感谢您提供代码详细信息。几个后续问题。那些花车是从哪里来的?当您进行捏合/展开缩放时,您是否将它们从地图中读取出来?地图视图的尺寸是多少?这很有趣,因为我在卸载视图时读取了这些值并将它们保存到 UserSettings。准确恢复我的视图没有问题。
        • 是的,我在缩放后从地图的区域属性中提取了这些。尺寸为 460 x 320,在 iPhone 4 模拟器中运行。
        • 真的很抱歉。我觉得自己像个白痴,因为我也看到了这种行为,它似乎因缩放级别和位置而异,我没有通过捏缩放进行充分测试。我想知道通过新的 mapRectThatFits: edgePadding: 工作是否可行。我会尝试一下,然后转回来。再次为我的混淆道歉。
        • 不要道歉!听起来很明显,很容易错过。我也花了一段时间才注意到。我只是松了一口气,你能够确认问题。再次感谢您的调查。一个旁注。您会注意到,如果您恢复通过双击而不是捏缩放设置的区域,这不是问题;进一步证明 setRegion 正在“捕捉”其给定的区域。这就是为什么我花了一段时间才注意到的原因。在模拟器中双击缩放比捏缩放要容易得多,所以我总是点击缩放。
        【解决方案7】:

        如果您在 InterfaceBuilder 中设置 MapView,请确保不要这样做:

        _mapView = [[MKMapView alloc] init];

        一旦我删除了这个初始化行,我的地图视图突然开始正确响应我发送的所有更新。我怀疑发生的情况是,如果您执行 alloc init,它实际上是在创建另一个视图,该视图没有在任何地方显示。你在屏幕上看到的是你的笔尖初始化的那个。但是,如果您分配 init 一个新的,那么这是其他地方的东西,它不会做任何事情。

        【讨论】:

          猜你喜欢
          • 2013-09-07
          • 2012-12-15
          • 1970-01-01
          • 2015-12-07
          • 2014-04-01
          • 2011-02-22
          • 2011-11-27
          • 2014-01-06
          • 1970-01-01
          相关资源
          最近更新 更多