MapKit框架详细解析(六) —— 添加自定义图块(一)
版本记录
版本号 | 时间 |
---|---|
V1.0 | 2019.04.25 星期四 |
前言
MapKit框架直接从您的应用界面显示地图或卫星图像,调出兴趣点,并确定地图坐标的地标信息。接下来几篇我们就一起看一下这个框架。感兴趣的看下面几篇文章。
1. MapKit框架详细解析(一) —— 基本概览(一)
2. MapKit框架详细解析(二) —— 基本使用简单示例(一)
3. MapKit框架详细解析(三) —— 基本使用简单示例(二)
4. MapKit框架详细解析(四) —— 一个叠加视图相关的简单示例(一)
5. MapKit框架详细解析(五) —— 一个叠加视图相关的简单示例(二)
开始
首先看下写作环境
Swift 4, iOS 11, Xcode 9
在这个MapKit教程中,您将学习如何创建和使用自定义地图图块(map tiles)
,使您的应用中的地图独一无二,从而在竞争中脱颖而出。 您还将了解如何设置自定义位置注释并动态呈现叠加层。
地图在现代应用程序中无处不在。 地图提供附近景点的位置,帮助用户在城镇或公园中导航,查找附近的朋友,跟踪旅程中的进度,或提供增强现实游戏的背景。
不幸的是,这意味着从应用到应用,大多数地图看起来都一样。
本教程介绍如何包含手绘地图(Hand-drawing)
,而不是编程生成的地图,如Pokemon Go
中的地图。
手绘地图是一项重大的工作。 鉴于平面的大小,它只适用于定义明确,地理位置较小的区域。 如果您的地图有一个明确定义的区域,那么自定义地图可以为您的应用添彩。
MapQuest
是一款有趣的冒险游戏的开始。这位英雄在现实生活中在纽约中央公园附近奔跑,但他们开始冒险,与怪物搏斗,并在另类现实中收集宝藏。它有一个可爱,幼稚的设计,让玩家感觉舒适,并表明游戏并不严肃。
游戏中有几个兴趣点Points of Interest (POI)
,用于定义玩家可以与游戏互动的位置。这些可以是任务,怪物,商店或其他游戏元素。在POI周围进入一个10米的区域会开始相遇。为了本教程的目的,实际的游戏玩法和地图渲染相比没那么重要。
项目中有两个繁重的文件:
- MapViewController.swift:管理地图视图并处理用户交互逻辑和状态更改。
- Game.swift:包含游戏逻辑,以及管理一些游戏对象的坐标。
游戏的主要视图是MKMapView
。 MapKit
使用各种缩放级别的图块填充其视图,并提供有关地理要素,道路等的信息。
地图视图可以显示传统的道路地图或卫星图像。这对于在城市中航行非常有用,但对于想象你在中世纪的世界中进行冒险却毫无用处。但是,MapKit
可让您提供自己的地图艺术,以自定义美学和呈现信息。
地图视图由许多在您平移视图时动态加载的图块组成。这些图块是256像素乘256像素,并且排列在与墨卡托地图投影相对应的网格中。
要查看正在运行的地图,请构建并运行该应用。
哇! 多么美丽的小镇。 游戏的主要界面是一个地区,这意味着如果不进入访问中央公园就没有任何东西可以看和做。
Testing Out Location
与其他教程相反,MapQuest
是一个功能强大的应用程序! 但是,除非你住在纽约市,否则你有点不走运。 幸运的是,XCode至少有两种模拟位置的方法。
1. Simulating a Location
应用程序仍在iPhone模拟器中运行,设置用户的位置。
转到Debug \ Location \ Custom Location ...
。
将纬度设置为40.767769
,将经度设置为-73.971870
。 这将激活蓝色用户位置点并将地图聚焦在中央公园动物园上。 这是一个野生妖精生活的地方;你将被迫与它战斗,然后收集它的宝藏。
殴打无助的妖精后,你将被放置在动物园里(注意蓝点)。
2. Simulating an Adventure
静态位置对于测试许多基于位置的应用程序非常有用。 但是,这个游戏需要访问多个位置作为冒险的一部分。 模拟器可以模拟跑步,骑车和驾驶的不断变化的位置。 这些预先包含的旅行是为了Cupertino
,但MapQuest
只在纽约遇到过。
诸如此类的场合要求使用GPX
文件(GPS
交换格式)进行模拟定位。 此文件指定了许多航点,模拟器将在它们之间插入路线。
创建此文件超出了本教程的范围,但示例项目包含一个供您使用的测试GPX
文件。
通过选择Product \ Scheme \ Edit Scheme ...
在XCode
中打开scheme
编辑器。
选择左窗格中的Run
,然后选择右侧的Options
选项卡。 在Core Location
部分中,单击Allow Location Simulation
的复选标记。 在Default Location
下拉列表中,选择Game Test
。
这意味着该应用程序将模拟在Game Test.gpx
中指定的航点之间移动。
建立并运行。
模拟器现在将从第五大道地铁步行到中央公园动物园,在那里你将不得不再次与妖精战斗。在那之后,你可以去你最喜欢的水果公司的旗舰店买一把升级的剑。一旦循环完成,冒险将重新开始。
Replace the Tiles with OpenStreetMap
OpenStreetMap是一个社区支持的地图数据开放数据库。该数据可用于生成Apple Maps
使用的相同类型的地图图块。Open Street Map
社区提供的不仅仅是基本的道路地图,例如地形,自行车和艺术渲染的专用地图。
注意:Open Street Map tile policy对数据使用,归因和API访问有严格要求。这适用于教程,但在使用生产应用程序中的图块之前检查合规性。
1. Creating a New Overlay
替换地图图块需要使用MKTileOverlay
在默认Apple Maps
上显示新图块。
打开MapViewController.swift
,并将setupTileRenderer()
替换为以下内容:
func setupTileRenderer() {
// 1
let template = "https://tile.openstreetmap.org/{z}/{x}/{y}.png"
// 2
let overlay = MKTileOverlay(urlTemplate: template)
// 3
overlay.canReplaceMapContent = true
// 4
mapView.add(overlay, level: .aboveLabels)
//5
tileRenderer = MKTileOverlayRenderer(tileOverlay: overlay)
}
默认情况下,MKTileOverlay
支持通过模板化的URL
加载切片以获取切片路径。
- 1) 这是用于获取地图图块的
Open Street Map
的API。{x}
,{y}
和{z}
在运行时由单个tile
的坐标替换。 z坐标或缩放级别由用户在地图中放大的程度指定。x
和y
是给定地面部分的图块的索引。需要为每个支持的缩放级别的x和y提供一个tile
。 - 2) 创建叠加层
overlay
。 - 3) 指示图块不透明并替换默认地图图块。
- 4) 将叠加层添加到
mapView
。自定义图块可以位于道路上方或标签上方(如道路和地名)。打开的街道地图图块是预先贴上标签的,因此它们应该高于Apple的标签。 - 5) 创建一个处理图块绘制的图块渲染器。
在显示图块之前,必须使用MKMapView
设置图块渲染器,以便绘制图块。
在viewDidLoad()
的底部添加以下行:
mapView.delegate = self
这将MapViewController
设置为其mapView
的代理。
接下来,在MapView Delegate
扩展中,添加以下方法:
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
return tileRenderer
}
叠加渲染器告诉地图视图如何绘制叠加层。 图块渲染器是用于加载和绘制地图图块的特殊子类。
就这些! 构建并运行以查看标准Apple地图替换为Open Street Map
。
此时,您可以真正看到open source
地图和Apple Maps
之间的区别!
Dividing up the Earth
图块叠加层的神奇之处在于能够将图块路径转换为特定图像资源。 图块的路径由坐标表示:x,y和z。 x和y对应于地图表面上的索引,0,0
是左上方的图块。 z
坐标用于缩放级别,并确定构成整个地图的图块数量。
在缩放级别0
,整个世界由1×1网格表示,需要一个图块:
在缩放级别1
,整个世界被划分为2×2
网格。 这需要四个图块:
在级别2
,行数和列数再次翻倍,需要16
个图块:
此模式继续,每个缩放级别的细节级别和平铺数量翻两番。每个缩放级别需要2^2 * z
个图块,一直到缩放级别19
需要274,877,906,944
个图块!
Creating Custom Tiles
由于地图视图设置为遵循用户的位置,因此默认缩放级别设置为16
,这显示了良好的细节级别,以便为用户提供他们所在位置的上下文。缩放等级16
将需要4,294,967,296
个整个平面的图块!手绘这些图块需要一辈子的时间。
拥有像城镇或公园这样较小的有界区域可以创建自定义艺术作品。对于更大范围的位置,可以从源数据以程序方式生成图块。
由于此游戏的图块已预先渲染并包含在资源包中,因此您只需加载它们即可。遗憾的是,通用URL
模板是不够的,因为如果渲染器请求应用程序中未包含的数十亿个切片之一,则最好优雅地失败。
为此,您需要一个自定义的MKTileOverlay
子类。打开AdventureMapOverlay.swift
并添加以下代码:
class AdventureMapOverlay: MKTileOverlay {
override func url(forTilePath path: MKTileOverlayPath) -> URL {
let tileUrl = "https://tile.openstreetmap.org/\(path.z)/\(path.x)/\(path.y).png"
return URL(string: tileUrl)!
}
}
这将设置子类,并使用带有专用URL
生成器的模板URL替换基本类。
暂时保留Open Street Map
图块以测试自定义叠加层。
打开MapViewController.swift
,并将setupTileRenderer()
替换为以下内容:
func setupTileRenderer() {
let overlay = AdventureMapOverlay()
overlay.canReplaceMapContent = true
mapView.add(overlay, level: .aboveLabels)
tileRenderer = MKTileOverlayRenderer(tileOverlay: overlay)
}
这在自定义子类中交换。
建立并再次运行。 如果一切顺利,游戏应该与以前完全一样。 好极了!
1. Loading the Prerendered Tiles
有趣的来了。 打开AdventureMapOverlay.swift
,并用以下内容替换url(forTilePath :)
:
override func url(forTilePath path: MKTileOverlayPath) -> URL {
// 1
let tilePath = Bundle.main.url(
forResource: "\(path.y)",
withExtension: "png",
subdirectory: "tiles/\(path.z)/\(path.x)",
localization: nil)
guard let tile = tilePath else {
// 2
return Bundle.main.url(
forResource: "parchment",
withExtension: "png",
subdirectory: "tiles",
localization: nil)!
}
return tile
}
此代码加载游戏的自定义图块。
- 1) 首先,尝试使用已知的命名
scheme
在资源包中找到匹配的图块。 - 2) 如果没有提供图块,它将被羊皮纸
parchment
图案取代,使地图具有幻想中世纪的感觉。 这也消除了为每个平铺路径提供唯一资产的需要。
建立并再次运行。 现在显示自定义地图。
尝试放大和缩小以查看不同级别的细节。
2. Bounding the Zoom Level
不要放大或缩小太多,否则你将完全丢失地图。
幸运的是,这是一个简单的修复。 打开MapViewController.swift
,将以下行添加到setupTileRenderer()
的底部:
overlay.minimumZ = 13
overlay.maximumZ = 16
这会通知mapView
只在这些缩放级别之间提供图块。 将缩放更改为缩放应用程序中提供的平铺图像。 没有提供额外的细节,但至少现在显示的图像与比例相匹配。
后记
本篇主要介绍了添加自定义图块,感兴趣的给个赞或者关注~~~