地形
加载地形
WARNING
地形功能需要你使用 maptalks-gl
mapbox
const map = new maptalks.Map("map", {
// "center": [119.09557457, 30.14442343, 339.73126220703125], "zoom": 11.856275713521464, "pitch": 61.80000000000011, "bearing": -64.07337236948052,
center: [108.95986733, 34.21997952, 430.3062438964844],
zoom: 12.698416480987284,
pitch: 0,
bearing: 1.8437368186266667,
// cameraInfiniteFar: true,
// heightFactor: 4.2,
zoomControl: true,
// baseLayer: new maptalks.TileLayer('base', {
// urlTemplate: "https://webrd01.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}",
// subdomains: ["a", "b", "c", "d"],
// })
});
const sceneConfig = {
postProcess: {
enable: true,
antialias: {
enable: true,
},
},
};
const colors4 = [
[0, "#F0F9E9"],
[200, "#D7EFD1"],
[400, "#A6DCB6"],
[650, "#8FD4BD"],
[880, "#67C1CB"],
[1100, "#3C9FC8"],
[1300, "#1171B1"],
[1450, "#085799"],
[1600, "#084586"],
];
const terrain = {
type: "mapbox",
// tileSize: 256,
maxAvailableZoom: 14,
requireSkuToken: false,
urlTemplate: "mapbox terrain server url",
subdomains: ["a", "b", "c", "d"],
colors: colors4,
exaggeration: 4,
};
const group = new maptalks.GroupGLLayer("group", [], {
terrain,
});
group.addTo(map);
// map.setView({
// "center": [120.06864866, 30.12911788, 430.3062438964844], "zoom": 12.814714048170666, "pitch": 56.45000000000009, "bearing": 2.7148818684635216
// })
map.setView({
center: [118.25079334, 30.1210681, 430.3062438964844],
zoom: 11.892086520379873,
pitch: 66.64999999999992,
bearing: 112.90705084326646,
});
天地图
WARNING
天地图地形需要设置地图投影为 EPSG:4326
map = new maptalks.Map("map", {
center: [114.3404041441181, 30.548730054693106],
zoom: 10,
spatialReference: {
projection: "EPSG:4326",
},
});
const sceneConfig = {
postProcess: {
enable: true,
antialias: {
enable: true,
},
},
};
const skinLayers = [
// baseLayer
];
const colors4 = [
[0, "#F0F9E9"],
[200, "#D7EFD1"],
[400, "#A6DCB6"],
[650, "#8FD4BD"],
[880, "#67C1CB"],
[1100, "#3C9FC8"],
[1300, "#1171B1"],
[1450, "#085799"],
[1600, "#084586"],
];
const terrain = {
type: "tianditu",
// tileSize: 256,
// terrainWidth: 65,
shader: "lit",
maxAvailableZoom: 12,
// tileSystem: [1, -1, -180, 90],
urlTemplate:
"https://t{s}.tianditu.gov.cn/mapservice/swdx?T=elv_c&tk=your key&x={x}&y={y}&l={z}",
subdomains: ["1", "2", "3", "4", "5"],
colors: colors4,
exaggeration: 4,
material: {
baseColorFactor: [1, 1, 1, 1],
outputSRGB: 1,
roughnessFactor: 0.69,
metallicFactor: 0,
},
};
const group = new maptalks.GroupGLLayer("group", skinLayers, {
terrain,
});
group.addTo(map);
map.setView({
center: [118.25079334, 30.1210681, 430.3062438964844],
zoom: 9.892086520379873,
pitch: 66.64999999999992,
bearing: 112.90705084326646,
});
cesium/cesiumlab等cesium系
WARNING
cesium地形需要设置地图投影为 EPSG:4326
map = new maptalks.Map("map", {
center: [114.3404041441181, 30.548730054693106],
zoom: 10,
spatialReference: {
projection: "EPSG:4326",
},
});
const sceneConfig = {
postProcess: {
enable: true,
antialias: {
enable: true,
},
},
};
const skinLayers = [
// baseLayer
];
const colors4 = [
[0, "#F0F9E9"],
[200, "#D7EFD1"],
[400, "#A6DCB6"],
[650, "#8FD4BD"],
[880, "#67C1CB"],
[1100, "#3C9FC8"],
[1300, "#1171B1"],
[1450, "#085799"],
[1600, "#084586"],
];
const terrain = {
// tileSystem: [1, 1, -180, -90],
// zoomOffset: -1,
maxAvailableZoom: 14,
type: "cesium-ion", //or cisium 本地部署的服务就cesium
// terrainWidth: 65,
accessToken: "your token",
urlTemplate:
"https://assets.ion.cesium.com/asset_depot/1/CesiumWorldTerrain/v1.2/{z}/{x}/{y}.terrain",
shader: "lit",
colors: colors6,
exaggeration: 4,
};
const group = new maptalks.GroupGLLayer("group", skinLayers, {
terrain,
});
group.addTo(map);
map.setView({
center: [118.25079334, 30.1210681, 430.3062438964844],
zoom: 11.892086520379873,
pitch: 66.64999999999992,
bearing: 112.90705084326646,
});
地形动态设置
const terrain = {
// tileSystem: [1, 1, -180, -90],
// zoomOffset: -1,
maxAvailableZoom: 14,
type: "cesium-ion", //or cisium 本地部署的服务就cesium
// terrainWidth: 65,
accessToken: "your token",
urlTemplate:
"https://assets.ion.cesium.com/asset_depot/1/CesiumWorldTerrain/v1.2/{z}/{x}/{y}.terrain",
shader: "lit",
colors: colors6,
exaggeration: 4,
};
//set terrain
group.setTerrain(terrain);
//remove terrain
group.removeTerrrain();
获取地形当前鼠标位置的数据
核心库从v1.0.0-rc.33开始,地图的事件系统我们加了地形相关的信息数据了,直接从事件属性读取对应的值即可
map.on("click mousemove", (e) => {
console.log(e.terrain);
});
WARNING
- 要求核心库版本 >=v1.0.0-rc.33
- terrain的值可能为空,取决当前鼠标位置的地形数据是否加载完成,所以做好null判断
地形着色和夸张
新版本的maptalks-gl里添加了地形着色的支持
const colors5 = [
[0, "#FBFBFB"],
[200, "#D6D6D6"],
[400, "#BEBEBE"],
[650, "#AFAFAF"],
[880, "#868686"],
[1100, "#5E5E5E"],
[1300, "#3F3F3F"],
[1450, "#2B2B2B"],
[1600, "#070707"],
];
const terrain = {
type: "mapbox",
// tileSize: 256,
maxAvailableZoom: 14,
requireSkuToken: false,
urlTemplate: "your server url",
subdomains: ["a", "b", "c", "d"],
colors: colors5,
exaggeration: 4,
};
const group = new maptalks.GroupGLLayer("group", [], {
terrain,
});
group.addTo(map);
地形着色过程中会自动颜色进行插值的的
- colors参数表示每个海拔段的颜色值,中间的值会自动进行插值
- exaggeration 表示地形夸张程度
效果怎样取决于你的视觉审美了
WARNING
地形着色和地形夸张目前不支持动态更新,即在配置好的那一刻就决定了效果
蒙层
地形支持setMask()
的,业务里根据我们的需求设置不同的mask即可:
- ClipInsideMask 内裁剪,挖洞
- ClipOutsideMask
- VideoMask
import { Circle, ClipInsideMask, GroupGLLayer } from "maptalks-gl";
const circle = new Circle([118.24874119019701, 30.13560350137501], 10000);
const shell = circle.getShell();
//ClipOutsideMask 是Polygon的子类,使用方式和Polygon一样
// const mask = new maptalks.ClipOutsideMask([shell]);
const mask = new ClipInsideMask([shell]);
const terrain = {
type: "mapbox",
// tileSize: 256,
maxAvailableZoom: 14,
requireSkuToken: false,
urlTemplate: "./../assets/data/tile-rgb/{z}/{x}/{y}.png",
subdomains: ["a", "b", "c", "d"],
colors: colors4,
exaggeration: 4,
};
const group = new GroupGLLayer("group", [], {
terrain,
});
group.addTo(map);
const terrainLayer = group.getTerrainLayer();
terrainLayer.setMask(mask);
地形自定义
INFO
该功能已经开发完成,我们将在下一个maptalks-gl
版本里发布
目前maptalks内置了mapbox
,天地图
,ceisum
的数据解码,但是用户侧仍然希望加载一些其他格式的地形数据 (例如mapzen
的地形瓦片),针对这个需求我们将会:
- 地形支持用户自定义加载瓦片和地形解码
- 针对用户自定义返回的数据格式将支持mapbox rgb tile(这个将是内部支持自定义的唯一格式)
- 任何其他任何格式的地形数据需要用户侧把数据转换成mapbox rgb tile
- 针对自定义地形格式是唯一的可以理解成: maptalks仅仅支持
GeoJSON
格式的,其他的格式(kml/osm/gpx等)请转换成GeoJSON
架构图
mapbox 地形数据编码函数
export function encodeMapBox(height: number, out?: [number, number, number]) {
const value = Math.floor((height + 10000) * 10);
const r = value >> 16;
const g = (value >> 8) & 0x0000ff;
const b = value & 0x0000ff;
if (out) {
out[0] = r;
out[1] = g;
out[2] = b;
return out;
} else {
return [r, g, b];
}
}
任何其他的地形格式需要你解码后再编码成mapbox格式的,你会用到这个函数的
自定义套路
和TileLayer
一样,地形里也提供了loadTileBitmap
自定义方法,方面用户对地形来进行各种自定义需求。
你可以在loadTileBitmap
里进行:
- 地形数据的请求/取消
- 地形数据的解码
- 地形数据的重新编码
- 地形噪点处理等
- 地形瓦片的剪裁等
- ...
要求:
- 返回的对象必须是
ImageBitMap
对象 - 地形数据编码必须是
mapbox
格式的,mapbox
地形编码请参考上面的代码, - 任何其他非
mapbox
编码的格式自己编码成mapbox
的格式返回给地形调度系统即可 - 理论上任何其他格式的地形数据都可以转换成
mapbox
的
group.on("terrainlayercreated", (e) => {
const terrainLayer = group.getTerrainLayer();
console.log(terrainLayer);
terrainLayer.getRenderer().loadTileBitmap = function (
url,
tile,
callback,
options,
) {
console.log(url);
//do some things
callback(null, imagebitmap);
};
});
tileclip里的地形编码
maptalks.tileclip 作为一个瓦片处理工具插件了, 为了方便大家接入其他格式的地形提供了常见的地形服务的转码工作,包括:
- mapzen
- Arcgis
- GQIS灰度图
- 其他等
group.on("terrainlayercreated", (e) => {
const terrainLayer = group.getTerrainLayer();
console.log(terrainLayer);
terrainLayer.getRenderer().loadTileBitmap = function (
url,
tile,
callback,
options,
) {
console.log(url);
tileActor
.encodeTerrainTile({
url: maptalks.Util.getAbsoluteURL(url),
terrainType: "mapzen", //arcgis qgis-gray etc
// timeout: 5000
})
.then((imagebitmap) => {
callback(null, imagebitmap);
})
.catch((error) => {
//do some things
console.error(error);
});
};
});
如果你的地形数据编码是自定义的,那么就需要你自己在业务自行解码地形数据,然后从新编码成mapbox
格式的
QGIS灰度图
maptalks.tileclip 里也提供了GQIS
灰度图灰度图 的重编码功能,这里我将着重介绍下它
至于mapzen
和arcgis
的,因为数据格式特定的而且是在线服务,直接调用即可,这里不做介绍了, 请查看 maptalks.tileclip的文档
动机
有好多群友遇到制作mapbox
地形服务没有好的工具,制作cesium
地形的工具可能要收费等,故我捣鼓了下看看能否 把 QGIS
里简单的灰度图也接入进去,经过尝试证明是可行的
制作
WARNING
注意我这里用的数据集是墨卡托投影的(EPSG:3857),有原始数据是 EPSG:32649转换来的的, 至于你的数据如果不是常规的投影的自己进行转换(转成EPSG:4326/EPSG:3857啥的)即可
- 将我们的地形数据(dem)拖到
GQIS
即可
WARNING
切记不要做任何修改,GQIS默认会自动根据地形数据里最小值和最大值进行黑白着色的
- 记录地形数据的最小值和最大值
图层属性里会显示的,待会我们进行地形的解码是会用到
- 切片
将图层切成普通的瓦片即可,这个是个漫长的过程,和你的数据覆盖范围和层级有关,一般我们可以先尝试切个0-14层级 这样,在地形测试中确认没有问题后,我们再切全量的层级的,以免出错了浪费大量的时间在切片上
WARNING
注意我这里采用的墨卡托的常见的互联网的切片规则(Google的那一套), 如果你需要其他的切片规则(比如EPSG:4326啥的)请自行设置切片规则
- 加载灰度图服务
group.on("terrainlayercreated", (e) => {
const terrainLayer = group.getTerrainLayer();
console.log(terrainLayer);
let min = 210,
max = 1980;
terrainLayer.getRenderer().loadTileBitmap = function (
url,
tile,
callback,
options,
) {
console.log(url);
tileActor
.encodeTerrainTile({
url: maptalks.Util.getAbsoluteURL(url),
terrainType: "qgis-gray",
minHeight: min,
maxHeight: max,
// timeout: 5000
})
.then((imagebitmap) => {
callback(null, imagebitmap);
})
.catch((error) => {
//do some things
console.error(error);
callback(null, maptalks.getBlankTile());
});
};
});
适用对象
- 如果你使用在线的地形服务即可满足你的需求,你不需要这些
- 如果你是有自己的dem数据想发布成地形瓦片服务的,就可以使用这个方式了,一般是有一个市/县啥的自己的数据
价值和意义
简化了我们制作地形瓦片的难度和成本,再也不用去找各种地形制作工具了,GQIS
足够了。
警告
WARNING
目前我测试下来没有发现问题,如果你使用过程中发现有问题请给 maptalks.tileclip 插件报告issue