Skip to content
目录

地形

加载地形

WARNING

地形功能需要你使用 maptalks-gl

mapbox

js
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,
});

terrain mapbox demo

天地图

WARNING

天地图地形需要设置地图投影为 EPSG:4326

js
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

加载cesiumlab 地形数据例子

js
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,
});

地形动态设置

js
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开始,地图的事件系统我们加了地形相关的信息数据了,直接从事件属性读取对应的值即可

js
map.on("click mousemove", (e) => {
    console.log(e.terrain);
});

WARNING

  • 要求核心库版本 >=v1.0.0-rc.33
  • terrain的值可能为空,取决当前鼠标位置的地形数据是否加载完成,所以做好null判断

地形着色和夸张

新版本的maptalks-gl里添加了地形着色的支持

%5BTD%40C%40%7DWARM79N(7182MPOG.png

js
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

terrain mask demo

terrain videomask demo

js
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

架构图

W(0%7D%7BS6_YN0QWHGO%60%24Z0XLU.png

mapbox 地形数据编码函数

ts
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
js
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灰度图
  • 其他等
js
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灰度图灰度图 的重编码功能,这里我将着重介绍下它

至于mapzenarcgis的,因为数据格式特定的而且是在线服务,直接调用即可,这里不做介绍了, 请查看 maptalks.tileclip的文档

动机

有好多群友遇到制作mapbox地形服务没有好的工具,制作cesium地形的工具可能要收费等,故我捣鼓了下看看能否 把 QGIS里简单的灰度图也接入进去,经过尝试证明是可行的

制作

WARNING

注意我这里用的数据集是墨卡托投影的(EPSG:3857),有原始数据是 EPSG:32649转换来的的, 至于你的数据如果不是常规的投影的自己进行转换(转成EPSG:4326/EPSG:3857啥的)即可

  • 将我们的地形数据(dem)拖到GQIS即可

WARNING

切记不要做任何修改,GQIS默认会自动根据地形数据里最小值和最大值进行黑白着色的

T))XK%7D(VHW9%600%5BRU~ZKZ7)0.png

  • 记录地形数据的最小值和最大值

图层属性里会显示的,待会我们进行地形的解码是会用到

RX8UM8%7BO%40G%25SN~Y)%7B%607%5DK%40S.png

  • 切片

将图层切成普通的瓦片即可,这个是个漫长的过程,和你的数据覆盖范围和层级有关,一般我们可以先尝试切个0-14层级 这样,在地形测试中确认没有问题后,我们再切全量的层级的,以免出错了浪费大量的时间在切片上

WARNING

注意我这里采用的墨卡托的常见的互联网的切片规则(Google的那一套), 如果你需要其他的切片规则(比如EPSG:4326啥的)请自行设置切片规则

  • 加载灰度图服务
js
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

maptalks教程 document auto generated by mdpress and vitepress