Mapbox 功能篇

Mapbox 功能篇

Mapbox 功能篇

 
6 人赞同了该文章
展开目录
 

序言

  2023 年金秋十月,公司组织团建旅游,小部分人选择去了桂林。秋天是个收获的季节,正好可以欣赏壮观的龙脊梯田画面,然天公不作美,时不时会遇上大雾小雨。我和组里的余总游览了桂林的山水,乘坐漓江的游船兜了一圈,就第五套人民币 20 元背面的图案所在地。大家登上船顶环望,拿出手机和 20 元拍照留念。我问余总:“桂林山水这么美,可以搬到我们迷你世界中来吗?”余总说:“当然了,要相信玩家的创造力,他们可以制作各式各样的精美地图。”

  回到公司工位上,怎样搬地图呢?地图厂商都会提供地理编码(geocoding)、地理定位(geolocation)、海拔高度(elevation)、路线查找(Direction)、地图块(Map Tile)数据等 API。在地图服务这一块儿,Google Maps 算是首屈一指的地图厂商了。但是 API 的免费调用次数锐减,每月费用陡增[1],导致开发者们寻求替代方案。而且啊, 从 2023 年开始 Google Maps 游戏服务标记为 deprecated[2]。在对比了多款地图供应商之后,我选择了体验 Mapbox。Mapbox 在费用上能接受,地图块请求数量在20万/月以下是免费的[3],而且我发现了想要的东西。

我的世界 桂林山水

仓库地址

  Mapbox Unity SDK 官方下载地址:。项目用的 Unity 2017.1.2,有 5 年多没更新了。有人直接 issue 发问:这个项目死了吗?[4]Mapbox 的开发者说还有很多工作要做,私有的仓库还没准备好开放。

  用 Visual Studio 编译会有多处关于 AR 的代码报错,可以直接注释掉。如果不需要 AR 功能,可以直接移除这些目录。

AR 文件夹

access token

  刚启动 UnityPlayer,工程有条关于 access token 的报错。

Mapbox.Unity.MapboxAccess+InvalidTokenException: TokenRevoked

  我们需要在菜单 Mapbox > Setup 里填写正确的 token。后面所有的 HTTP 请求都要求带上这个参数。那么哪里可以获取呢?我尝试从 Mapbox 官网网页版的 playground 里找,毕竟它请求也需要带上这个参数。我借用了 Boundary Explorer ,见下图网页,发现不行。看来必须走正道。

  正规渠道是注册 Mapbox 账户。Mapbox 在 2022 年下半年黯然离开中国市场。由于地区限制,国内无法注册账户。不得已需要FQ注册,然后注册账号后,还要绑定 VISA 卡。

  第一步,填国外邮箱。

Mapbox: Create your account

  第二步,要提供 VISA 卡信息,即使免费使用也要填写哦。我办有一个招商银行信用卡。

Payment Information

  注册完账号,就可以在账户页面创建 access token 了。在 Editor 界面点击菜单 Mapbox > Setup,在弹出的窗口里填上 access token,提交判断为绿色的 valid 后,方可正常使用 Mapbox API 请求数据。

  接下来点击上图中的场景图,单独体验 Mapbox 提供的功能。或者 File > Build And Run 生成可执行文件去运行。出现这个错误“Because you are not a member of this project this build will not access Unity services”看这里

功能体验

1. City Simulator

  提供了经纬度和缩放级别参数,就可以显示附近的道路和建筑风貌。地图块的坐标是 {zoomLevel}/{x}/{y} 三元组,Tile 的 HTTP 请求基本都会带上这个参数。

GameObject go = new GameObject("Map");
AbstractMap map = go.AddComponent<AbstractMap>();
Vector2d latLon = new Vector2d(22.535511, 113.926838);
int zoom = 18;
map.Initialize(latLon, zoomLevel);

2. Location based Game

  前面是固定视角的场景,这个是跟随 LocationProvider 而动的地图。如果打出 Android 平台包,并启用了 GPS 权限,可以看到直观的效果。AbstractMap 会根据当前的经纬度刷新地图。不要被名字骗了,最初我看到这个名字,还以为是抽象类。

  不过 UnityEngine.Input.location 封装的 LocationService 却是 float 精度[5],经纬度坐标应该提供 double 精度的。Android 和 iOS 的 API 都提供的是 double 精度。float 的32位由1个符号位,8个指数位,23个小数位组成,我在这里的知乎回答过。二进制表示的最小精度 2−23 转化成十进制 10𝑛 ,则 n 为 -23 * math.log10(2) = -6.923689900271568,即十进制数有大约7位数的精度。经度在 0 附近的角度精度最高,经度在超过 100 后,丢失了两位角度精度。

3. Data Explorer

  开始了解 AbstractMap 数据结构。Camera.main 挂上 CameraMovement.cs 脚本后,在地图上可以随意移动了。

  看左边的 Hierachy,AbstractMap 地图随 ExtentOptions 而加载多个地图块 Tile,每个 Tile 有 building、road、water、park 等 feature 的渲染(下面的俯视图)。

  上面选择的 ExtentOptions 是 CamereaBounds,后面我补充写了可以在建筑群中穿梭和跳跃的代码。平视导致需要加载很多 Tile,花费时间久,不得不将 ExtentOptions 改成 RangeAroundCenter(见下图),并朝四方各延伸一个地图块,所以固定显示 3x3=9 个 Tile。

4. Interactive Styled Vector Map

  搜索地名拉取矢量图块(Vector Tile)。鼠标可以精准选择每个建筑,选中后可以查看建筑的属性,type 是建筑的类型,分类有很多,streets-v7-stats.json 文件列举的有,这里说一部分 {"building", "house", "garage", "school", "church", "farm", "hospital", "shop", ...}

  发现建筑的高度数值不对,比如下图的科兴科学园,普遍当成只有一层楼,一层楼假设了 3米高。OpenStreetMap 有高度数据,Mapbox 没有把数据同步过来?也不对,看了 Android 平台 SDK,发现能正常显示高度。

  最后才摸清楚,需要修改 DataSource 为 Mapbox Streets v8 tileset。v8 数据还多出了 iso_3166_1,iso_3166_2 国家地区代码。点选建筑后可以知晓是哪个国家,哪个省的。

  框右侧黑色属性文字的显示对应 FeatureUiMarker.cs。鼠标 hover 到建筑的地方,建筑会是红色的材质。

  height 是建筑的垂直高度,单位米。min_height 是建筑的“低”度,一般取0,对于悬空的建筑和天桥,是一个正值。可以参考 OpenStreetMap 的链接[6]

  比上图多了森林树木的渲染,查看了一下代码实现。SpawnInsideModifier 可以在圈起的公园区域,spawn 出一堆高低不同的树木等。

 

5. Traffic And Directions

  选择 Driving / Walking / Cycling 后,分别对应车辆、步行、自行车的路线数据。HTTP 请求的参数有改动,但切换好像没看到有什么变化?

动图封面
 

  绿色、黄色、橙色红色分别标识交通拥塞程度。看到准备多个车辆的纹理,有低、中、高、严重等四张图片如下。用了 UV 的流动来实现,速度越快道路前行方向上的位移越大。代码很简单,在 TrafficUvAnimator.cs。

6. Astronaut Game

  换了建筑和花园的风格。车改成 mesh 穿梭。多了一个宇航员在地图中行走。指定 startPoint 和 endPoint,角色在地图上按返回的路径点信息行走。同上,RoutingProfile 可选 driving、walking、cycling,三者返回的路线数据可能不一样。

动图封面
 

  鼠标双击的屏幕坐标 RayCast 转成地图坐标,发送 HTTP 请求,返回 List<WayPoint> 信息,以此来控制角色的移动和旋转。相关代码如下:

// AstronautMouseController.cs
Ray ray = cam.ScreenPointToRay(Input.mousePosition);

if (Physics.Raycast(ray, out hit, Mathf.Infinity, layerMask))
{
        startPoint.position = transform.localPosition;
        endPoint.position = hit.point;
        MovementEndpointControl(hit.point, true);

        directions.Query(GetPositions, startPoint, endPoint, map);
}

  以上是发送网络请求返回路径,离线的话需要自己写 A* 搜索算法完成本地路径的计算。

7. POI Placement

  以下是搜索南山后,返回深圳大学附近的地图。地图上多了 POI(Points of interest)标记,根据 feature 的 type 属性选择展示不同的 prefab。

  以 billboard 方式显示 maki 图标和文字后,相机再挂上我们写的 FPS 脚本,就可以做在地图上寻宝箱之类的游戏了。

动图封面
 

 

8. Replace Features

  Mapbox 里用 TextureSideWallModifier 生成的建筑,都是简单的模型,提供平面多边形和高度数据后可以挤出 mesh。我们借助 ReplaceFeatureModifier 或 ReplaceFeatureCollectionModifier 来替换成高精度模型。模型都是 FBX 格式,文件路径如下:

  导入 Blender 后看了一下模型数据。左边是自由女神像(Statue of Liberty),右边是缩小后的帝国大厦(Empire State Building)。

  从三维软件里导出 FBX 格式,请保留四边形面(quadrilateral,缩写为 quad),不要全部三角化,因为 Unity 支持后续的工作。点击 fbx 文件,可以看到 Inspector 里的 Keep Quads[7] 选项没有勾选。quad 一般作曲面细分(Tessellation)用。n-gon(顶点数量超过四个)最终都会转换为三角形。勾选 Keep Quads 后,Unity 会将三角形和四边形分开存放,用上两个 submesh。

9. Playground

  游乐场演示了 Mapbox 各类 API 的使用。
  forward geocoding 演示根据指定的地名,获得具体的位置信息(比如经纬度、地址的全称等)。geo 是地理(Geography)的缩写。
  reverse geocoding 是相反的操作。根据给定的经纬度,获得具体的位置信息。

  geocoding 是地图的基本需求,比如查找附近的超市或银行等。HTTP 请求可以带上国家和语言参数。比如查找深圳南山区 https://api.mapbox.com/search/geocode/v6/forward?q=Nanshan&language=zh-Hans&access_token=ACCESS_TOKEN,返回的 feature 组数据里,除了经纬度信息外,还有区域的经纬度范围信息(下图的 bbox)、三级行政划分数据(中国/广东省/深圳市/南山区)。

  上图演示了拉取位图( Raster Tile)和矢量图(Vector Tile)数据。Vector Tile 是 protobuf 格式数据,官方文档在这里。上图请求经纬度附近的 feature 数据的内容是 json 格式。Raster/Vector Tile API 的请求上限是十万次每分钟,如果超过就需要收费了。API 都是请求次数小于多少可免费使用,超过多少开始收费,用户量越大则越便宜[8]

10. Zoomable Map

  可以通过搜索地名显示地图,可调整缩放级别。这个示例展示了 satellite-v9 的 Raster Tile 位图数据。通过加载 CustomMarkerPrefab.prefab,可以在 AbstractMap 地图上 spawn 一些标记。

11. Globe

  前面是 zoomLevel 较大,地图显示平面。这个是 zoomLevel 较小,显示为球面。运行时拖动 zoomLevel 可以看到散成一块块的。此时的 ElevationLayerType 为 GlobeTerrain,相关代码在 FlatSphereTerrainStrategy。

  对于每个 Terrain Tile,从 tile id 获取经纬度的范围,再转成角度,用代码生成球面几何数据。对写过生成球面顶点数据的人来讲,这个也不算太难。

12. Voxel Map

  受 Minecraft 启发,生成的像素风格地图。这个没有使用 AbstractMap,因为没有直接使用 Raster、Terrain、Vector 三种 Tile 的数据来渲染。

  下图则是上图对应的俯视图。

  渲染上图的数据来源是两张图—— RasterTile 和 TerrainTile。RasterTile 是一张俯视的位图,数据来源于链接 https://api.mapbox.com/styles/v1/mapbox/cjb3veyx9532a2tpjozhuv0u9/tiles/{zoomLevel}/{x}/{y}。绿色的是植被,蓝色的是水域。上下图的整体样貌对得上。

RasterTile

  TerrainTile 是高程图,数据来源于链接 https://api.mapbox.com/v4/mapbox.terrain-rgb/{zoomLevel}/{x}/{y}.pngraw。根据下面的公式,颜色会转换成高度信息。红色权重大,越红则越高。白色最高,黑色最低。

public static float GetAbsoluteHeightFromColor(Color color)
{
	return (float)(-10000 + ((color.r * 255 * 256 * 256 + color.g * 255 * 256 + color.b * 255) * 0.1));
}
TerrainTile

  通过这两张图,可以知晓要生成方块的总数量,以及各个的位置信息。每个方块的材质从 VoxelFetch 里计算获取。对比颜色,选择距离最接近的。

  以下是在 Editor 里配置的几个 VoxelColorMapper。颜色和对应的 Voxel 方块。

  上面的算法没有考虑悬空的景观,漓江的象鼻山、阳朔的月亮山等就无法正确展示,需要手工调整了。这个不像前面 Example 4 的建筑,提供了高度 height 和低度 min_height 数据。

参考

  1. ^Google Maps Platform Pricing https://developers.google.com/maps/billing-and-pricing/pricing
  2. ^Google Maps Platform gaming services (October 18, 2021 - December 31, 2022) https://developers.google.com/maps/deprecations#gaming-svcs-deprecation
  3. ^Mapbox pricing https://www.mapbox.com/pricing
  4. ^This project is dead?  https://github.com/mapbox/mapbox-unity-sdk/issues/1912
  5. ^Precision of Location longitude is worse when longitude is beyond 100 degrees https://forum.unity.com/threads/precision-of-location-longitude-is-worse-when-longitude-is-beyond-100-degrees.133192/
  6. ^简单的 3D 建筑 https://wiki.openstreetmap.org/wiki/Simple_3D_Buildings
  7. ^导入 FBX 模型 https://docs.unity3d.com/Manual/FBXImporter-Model.html
  8. ^Mapbox 各个 API 的价格 https://www.mapbox.com/pricing
编辑于 2024-06-22 17:18・IP 属地湖南

posted on 2024-10-21 21:14  漫思  阅读(198)  评论(0编辑  收藏  举报

导航