hz_gjf@163.com
three-tile 是一个使用 threejs开发的轻量级三维瓦片地图库,具有使用简单、资源占用少等优点,适用于给基于 threejs 开发应用增加三维地图。
注意:这不是Cesium,也不是Mapbox-gl,跟这些三维GIS框架没有一毛钱关系!
Source: https://github.com/sxguojf/three-tile
Examples: https://sxguojf.github.io/three-tile-example
Examples Source: https://github.com/sxguojf/three-tile-example
免责声明:
- 本框架不含任何地图数据,Example 中使用的地图均为直接调用第三方数据,使用中请遵循法律法规要求。
- 轻量级:地图以一个三维模型方式提供,不会对已有程序架构产生任何影响。
- 依赖少:整个框架仅有 threejs(R165)一个依赖。
- 速度快:对资源占用做极致优化,核显也能轻松跑到 60FPS。
- 使用简单:熟悉 threejs 基本上没有学习成本。
- 扩展性强:数据、模型、纹理、材质、渲染过程均能根据自己需要扩展和替换。
- 语言:TypeScript 100%
- IDE: VSCode
- 打包:Vite 4.0
- 依赖:three 0.165
<script src="./three.js"></script>
<script src="./three-tile.umd.js"></script>
npm i three-tile -S
yarn add three-tile -S
和一般 threejs 应用一样,初始化 threejs 三维场景后,将地图模型加入场景即可完成地图的显示。
使用按以下步骤:
- 初始化三维场景
- 定义地图数据源
- 创建地图模型
- 地图模型加入三维场景
three-tile 的三维场景初始化和 threejs 相同,按 threejs 的套路初始化场景、摄像机、控制器、灯光等即可。为了便于使用,three-tile 还提供一个 GLViewer 类,它封装了场景初始化过程,可直接使用它进行初始化。
import * as tt from "three-tile";
// 取得地图dom
const glContainer = document.querySelector<HTMLElement>("#map");
// 初始化三维场景(调用three-tile内置的初始化类)
const viewer = new tt.plugin.GLViewer(glContainer!);
如果你熟悉 threejs,场景初始化最好自己写,跟普通 threejs 程序并无太大差异。以下部分需要注意:
- 为了使地图坐标系与一般人类理解一致,three-tile 地图坐标方向采用东(x)北(y)上(z)方向,即地面在 x-y 平面上,海拔高度在 z 轴。而 threejs 一般平面在 xz 平面上,高度为 y 轴,所以初始化时需要使场景默认 up 指向 z 轴,可添加:Object3D.DEFAULT_UP.set(0, 0, 1) 即可。如果你的应用不能调整 up 值,可以将地图旋转-π/2° 完成。(一些朋友表示不适应,后期可能根据反馈调整)
- 地图添加光照才能显示。一般至少要有一个环境光,另外最好加一个直射光以通过地形法向量增强凹凸感。
- 场景控制器一般应用可使用 threejs 内置的 MapControls,其它控制器如 OrbitControls、FlyControls、PointerLockControls、TransformControls、FirstPersonControls 都能完美支持。
three-tile 内置了 Mapbox、ArcGis、Bing、天地图、高德、腾讯等多个厂商的瓦片地图源,可直接调用,也可根据根据需要自行扩展。地图切片默认使用 Google 方案,地形瓦片支持 MapBox 的 terrain-rgb 和 ArcGis 的 LERC 格式。如 MapBox 数据源创建如下:
import * as tt from "three-tile";
// MapBoxToken 请更换为你自己申请的key
const MAPBOXKEY = "xxxxxxxxxx";
// mapbox 影像数据源
const mapBoxImgSource = new tt.plugin.MapBoxSource({
token: MAPBOXKEY,
dataType: "image",
style: "mapbox.satellite",
});
// mapbox 地形数据源
export const mapBoxDemSource = new tt.plugin.MapBoxSource({
token: MAPBOXKEY,
dataType: "terrain-rgb",
style: "mapbox.terrain-rgb",
maxLevel: 15,
});
注意:
- 多数数据源需要申请 token 才能使用,请不要用我示例中的。
- 部分国外的地图无法访问,或是速度很慢,这个需要自己想办法,你们懂的。
- 国内地图使用“火星坐标系”,影像图与地形有可能无法套准。
- 地图数据使用,请遵循法律法规要求。
使用 TileMap 类的工厂方法 create() 创建地图,加入场景。
import * as tt from "three-tile";
// 创建地图
const map = tt.TileMap.create({
// 影像数据源
imgSource: mapBoxImgSource,
// 地形数据源
demSource: mapBoxDemSource,
// 地图投影**子午线经度
lon0: 90,
// 最小缩放级别
minLevel: 2,
// 最大缩放级别
maxLevel: 18,
});
// 将地图加入三维场景
viewer.scene.add(map);
也可调用 TileMap 的构造函数创建地图。
- 坐标轴:地图模型为东北上坐标系,即 X 轴指向东,Y 轴指向北,Z 轴指向上。与 threejs 默认坐标不同需要注意。
- 坐标单位:角度单位为弧度,经纬度为度,3857 投影(默认)距离单位为公里,4326 投影距离单位为 0.01 度,高度单位均为公里。
- 瓦片数据:内建的地图影像和地形数据均使用图片格式(jpg、png、webp 等),支持 3854 和 4326 投影,暂不支持矢量瓦片。
- 瓦片属性:默认情况瓦片模型纹理透明属性开启,请注意渲染顺序。
- 地图清晰度:清晰度与本框架无关,取决数据源精度。
- 地图标注源:大部分国内厂商地图数据的地名、边界、道路有一定偏移,与地形无法完全匹配。
- 地图 token:大部分厂商的地图数据需要申请开发 key 才能使用,three-tile 示例包含一些厂商的 token,访问的人多了厂商会封掉它们,使用者一定要自己申请(又不要钱)避免直接使用。
更多使用方法见 example
提供一个完整浏览器引入方式示例供测试,可不用 web 服务直接在文件系统下运行:
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"
/>
<title>three-tile</title>
</head>
<style>
html,
body {
background-color: #333;
height: 100%;
width: 100%;
padding: 0;
margin: 0;
display: flex;
}
#map {
flex: 1;
}
</style>
<body>
<div id="map"></div>
<script src="./three.js"></script>
<script src="./three-tile/three-tile.umd.cjs"></script>
<script>
console.log("three-tile start!");
// MapBoxToken 请更换为你自己申请的key
const MAPBOXKEY = "xxxxxxxxxx";
// mapbox影像数据源
const mapBoxImgSource = new tt.plugin.MapBoxSource({
token: MAPBOXKEY,
dataType: "image",
style: "mapbox.satellite",
});
// mapbox地形数据源
const mapBoxDemSource = new tt.plugin.MapBoxSource({
token: MAPBOXKEY,
dataType: "terrain-rgb",
style: "mapbox.terrain-rgb",
maxLevel: 15,
});
// 创建地图
const map = tt.TileMap.create({
// 影像数据源
imgSource: mapBoxImgSource,
// 地形数据源
demSource: mapBoxDemSource,
// 地图投影**子午线经度
lon0: 90,
// 最小缩放级别
minLevel: 2,
// 最大缩放级别
maxLevel: 18,
});
// 地图中心经纬度转为场景坐标
const center = map.geo2pos(new THREE.Vector3(108.942, 34.2855));
const container = document.querySelector("#map");
const viewer = new tt.plugin.GLViewer(
container,
new THREE.Vector3(center.x, center.y, 0),
new THREE.Vector3(center.x, center.y, 3e4),
);
viewer.scene.add(map);
// 动画漫游到3500km高空
(() => {
const timer = setInterval(() => {
const pos = viewer.camera.position;
pos.z -= 500;
pos.y -= 10;
if (pos.z < 100) {
clearInterval(timer);
}
}, 10);
})();
</script>
</body>
</html>