2GIS触手可及。 我们如何将地图添加到Apple Watch


Apple Watch迅速获得普及,并成为世界上最受欢迎的手表,领先于Rolex和其他制造商。 自2015年以来,在2GIS办公室就一直致力于创建手表应用程序。


在我们之前,只有Apple自己发布了功能完善的应用程序,手表上有卡。 Yandex.Map应用程序仅显示交通小工具以及回家和上班的时间。 手表通常不提供Yandex.Navigator,Google Maps,Waze和Maps.Me。


实际上,由于系统的许多局限性和开发的复杂性,公司要么根本不制作手表应用程序,要么使它们变得非常简单。 您不能只是在手表上绘制地图。 但是我们可以。


窥探一下猫,找出宠物项目如何成长为完整的产品。


UPD。:https://github.com/teanet/DemoWatch


我们决定制作一张地图。 一开始是什么?


  1. 值班的开发经验-测试项目工作2天。
  2. SpriteKit体验-0天。
  3. MapKit写作经验-0天。
  4. 怀疑可能出问题了-∞。

迭代1-思想飞扬


我们是认真的人,因此一开始我们决定制定工作计划。 我们考虑到我们在计划周密的冲刺中工作,对于“小产品任务”有五个故​​事点,并且完全不知道从哪里开始。


地图是一张非常大的图片。 我们能够在时钟上显示图片,这意味着我们可以处理卡的显示。


我们提供的服务可以将卡切成碎片:



如果您剪切此类图片并放入WKImage,我们将以5美分的价格获得最简单的工作原型。


并且,如果您将PanGesture添加到该图片并在每次滑动时安装新图片,我们将获得与卡交互的模拟。


/欢乐/听起来很糟糕,看起来差不多,效果更糟,但实际上任务已完成。


迭代2-最小原型


连续下载图像对于几个小时的电池来说是昂贵的。 是的,启动时间本身会受到影响。 我们希望获得更完整,更快速的响应。 在我们的耳角,我们听说这款手表支持SpriteKit-唯一的WatchOS框架,能够自己使用坐标,缩放和自定义所有这些出色功能。


经过几个小时的StackOverflow驱动开发(SDD),我们获得了第二个迭代:
一个SKSpriteNode,一个WKPanGestureRecognizer。



/欢喜/是的,这是6戈比的MapKit,可以正常使用。 紧急释放!


迭代3-添加平铺和缩放


当情绪沉睡时,他们想知道下一步该怎么做。


理解最重要的是:


  • 用图块替换图像。
  • 将4个图块附加到应用程序捆绑并将它们连接在一起。
  • 提供缩放图片。
    让我们将4个图块放入应用程序捆绑包中,然后将它们放在特定的一个上:

let rootNode = SKSpriteNode() 

借助简单的数学,我们将它们连接在一起。
我们通过WKCrownDelegate进行缩放:


 internal func crownDidRotate( _ crownSequencer: WKCrownSequencer?, rotationalDelta: Double ) { self.scale += CGFloat(rotationalDelta * 2) self.rootNode.setScale(self.scale) } 


/欢喜/好吧,现在可以肯定了! 一些修复,并掌握。


迭代4-优化与地图的交互


第二天,事实证明对于SpriteKit而言,anchorPoint不会影响缩放。 缩放完全忽略了anchorPoint,并且相对于rootNode的中心发生。 事实证明,对于每个缩放步骤,我们都需要调整位置。


从服务器加载图块,而不是将整个世界都存储在手表的内存中,这也很好。
不要忘记,图块应与坐标系在一起,以使它们不位于SKScene的中心,而应位于地图上的适当位置。


磁贴看起来像这样:



每个zoomLevel(以下称为“ z”)都有自己的图块集。 对于z = 1,我们有4个组成整个世界的图块。



对于z = 2-为了覆盖整个世界,您已经需要16个图块,
对于z = 3-64个图块。
对于z = 18≈68 * 10 ^ 9个图块。
现在需要将它们放到SpriteKit的世界中。


一张图块的大小为256 * 256 pt,这意味着
对于z = 1,“世界”的大小将为512 * 512 pt,
对于z = 2,“世界”的大小将等于1024 * 1024 pt。
为了便于计算,请按如下所示将磁贴放置在世界上:



编码磁贴:


 let kTileLength: CGFloat = 256 struct TilePath { let x: Int let y: Int let z: Int } 

在这样的世界中定义图块的坐标:


 var position: CGPoint { let x = CGFloat(self.x) let y = CGFloat(self.y) let offset: CGFloat = pow(2, CGFloat(self.z - 1)) return CGPoint(x: kTileLength * ( -offset + x ), y: kTileLength * ( offset - y - 1 )) } var center: CGPoint { return self.position + CGPoint(x: kTileLength, y: kTileLength) * 0.5 } 

该位置非常方便,因为它使您可以将所有东西都带入现实世界的坐标中:纬度/经度= 0,它正好位于“世界”的中心。


现实世界的纬度/经度按如下方式转换为我们的世界:


 extension CLLocationCoordinate2D { //     ( -1 < TileLocation < 1 ) func tileLocation() -> CGPoint { var siny = sin(self.latitude * .pi / 180) siny = min(max(siny, -1), 1) let y = CGFloat(log( ( 1 + siny ) / ( 1 - siny ))) return CGPoint( x: kTileLength * ( 0.5 + CGFloat(self.longitude) / 360 ), y: kTileLength * ( 0.5 - y / ( 4 * .pi ) ) ) } //       zoomLevel func location(for z: Int) -> CGPoint { let tile = self.tileLocation() let zoom: CGFloat = pow(2, CGFloat(z)) let offset = kTileLength * 0.5 return CGPoint( x: (tile.x - offset ) * zoom, y: (-tile.y + offset) * zoom ) } } 

随着缩放水平倾斜的问题。 我不得不花几天的时间来整理整个数学仪器,并确保瓷砖的完美融合。 也就是说,理想情况下,z = 1的图块应分为四个,z = 2,反之亦然,z = 2的四个图块应进入一个z = 1的瓦片。



另外,由于缩放比例从1 <= z <= 18变化,并且地图比例像2 ^ z,因此有必要将线性缩放变成指数缩放。


通过不断调整图块的位置可以提供平滑的缩放。 重要的是,图块必须精确地缝在中间:也就是说,第1级图块会分成4个第2级图块,缩放比例为1.5。


SpriteKit使用引擎盖下的浮子。 对于z = 18,我们得到了一个坐标范围(-33 554 432/33 554 432),并且float的精度为7位。 在出口处,我们的误差在30磅左右。 为了避免两半之间出现“间隙”,我们将可见的贴图放置在尽可能靠近SKScene中心的位置。


/欢喜/在完成所有这些手势之后,我们准备好原型进行测试。


发布日期


由于该应用程序确实没有传统知识,因此我们找到了一些志愿者进行一些测试。 他们没有发现任何特殊问题,因此决定将其推广到一边。


释放之后,事实证明,在第一个系列的时钟上,处理器没有时间在10秒内绘制卡的第一帧,并且由于超时而掉线。 我必须首先创建一张完全空的卡,以在10秒内装满,然后逐渐加载基材。 首先开发地图的所有级别-然后为其加载图块。


花费大量时间调试网络,正确配置缓存和较小的内存占用空间,以使WatchOS不会试图尽可能长时间地杀死我们的应用程序。


对应用程序进行了概要分析后,我们发现可以使用Retina磁贴而不是通常的磁贴,而几乎不会对加载时间和内存消耗造成任何损害,并且在新版本中,它们已经切换到了它们。


未来的结果和计划


我们已经可以在地图上显示具有在主应用程序中构建的机动路线的路线。 该功能将在即将发布的版本中提供。


该项目最初似乎是不可能的,但事实证明对我个人非常有用。 我经常使用该应用程序来了解是否应该在正确的站点下车。 我相信在冬天它会更加有用。


同时,他再次确信,项目的复杂性,他人对任务成功的信念或工作时间的空闲并不是那么重要。 最主要的是要制定一个项目的愿望,以及朝着目标的无聊而渐进的运动。 结果,我们有了一个成熟的MapKit,它几乎是无限的,并且可以与3 WatchOS一起使用。 可以根据需要进行修改,而无需等待Apple推出适当的API进行开发。


PS对于那些感兴趣的人,我可以安排一个完成的项目。 那里的代码级别远非生产。 但是,根据军事原则,它的运作方式并不重要,主要的是它的运作!

Source: https://habr.com/ru/post/zh-CN422935/


All Articles