Skip to content
目录

地图使用分度带坐标系并叠加墨卡托底图

有时业务里需要使用分度带的平面坐标系且希望能叠加天地图效果,这里分度带的平面坐标系指的地图采用这个坐标系

  • maptalks是支持平面坐标系的(identity )
js
var map = new maptalks.Map("map", {
    center: [0, 0],
    zoom: 4,
    spatialReference: {
        projection: "identity",
        resolutions: [32, 16, 8, 4, 2, 1],
        fullExtent: {
            top: 10000,
            left: -10000,
            bottom: -10000,
            right: 10000,
        },
    },
});
  • 天地图是4326或者3857投影的,如何把其加到地图上是难点所在

  • maptalks里当瓦片图层的投影和地图不同时是么有自动投影功能的,所以需要我们自己手动的简单的进行投影的转换

这里我们以EPSG:4547作为一个例子来介绍下怎么叠加天地图底图

HJO9BQEU3KUR%5D13F~%40%408)SH.png

js
const MAXBBOX = [344577.88, 2380651.49, 655422.12, 5036050.38];

WARNING

注意这里我们只处理了4547覆盖的范围,其他的区域一律不处理的,代码里你会看到我们使用简单的bbox相交判断的, 和4547不相交的瓦片我们直接忽略的

底图选择

天地图有EPSG:4326和EPSG:3857两种投影底图,考虑到EPSG:3857是常用的互联网底图,且相关工具包比较丰富,故这里我们选择了EPSG:3857 投影的底图,因为天地图访问需要token,而我不希望token泄漏,故使用了高德底图作为代替了

js
urlTemplate: "https://webst01.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}",

构造地图投影

因为我们选择了EPSG:3857的底图,故这里投影的切图参数采用标准的墨卡托切图参数, 如果不懂的可以参阅相关文档,即谷歌/MapBox等地图的默认切图参数

注意瓦片图层我们没有设置投影信息,它的投影信息将从地图上继承,即也是 identity

js
const resolutions = [];
const first = 156543.03392800014;
for (let i = 0; i <= 22; i++) {
    resolutions.push(156543.03392800014 / Math.pow(2, i));
}
console.log(resolutions);
// -20037508.342787,20037508.342787
// const width = 20037508;

//4547 bounds
const MAXBBOX = [344577.88, 2380651.49, 655422.12, 5036050.38];
var map = new maptalks.Map("map", {
    center: [529003.4558741893, 3381427.097063985],
    centerCross: true,
    zoom: 16,
    spatialReference: {
        projection: "identity",
        resolutions,
        //4547 bounds
        fullExtent: {
            top: 5036050.38,
            left: 344577.88,
            bottom: 2380651.49,
            right: 655422.12,
        },
    },
});

const baseLayer = new maptalks.TileLayer("base0", {
    // tileSystem: [1, -1, -5273200, 7202100],
    // spatialReference: {
    //     projection: 'EPSG:3857'
    // },
    // decodeImageInWorker: true,
    maxAvailableZoom: 18,
    urlTemplate:
        "https://webst01.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}",
});

baseLayer.addTo(map);

难点分析

因为地图现在的坐标系是4547的平面坐标,输入输出都是,我们可以得到的一些信息有:

  • 可以拿到瓦片的范围坐标范围 4547的
js
const tileBBOX = baseLayer._getTileBBox(tile);
  • 可以利用 proj4等工具将4547转成 其他投影的坐标的,比如4326/3857啥的
  • 我们也可以根据一个瓦片范围(4326)去计算这个瓦片覆盖哪些瓦片集合(3857)的,比如工具包 tile-cover

%7DDPU%60UC6IDHRC(C%7DVTF%7D%5B4S.png

  • 如果我们可以把覆盖的这些瓦片拼接起来再根据瓦片的范围去剪裁这个拼接好的大的瓦片, 那么我们就可以实时的计算当前瓦片对应的3857的瓦片了对不对?

步骤

  • 计算当前瓦片的范围,注意这个范围是4547的
  • 将这个范围转成 4326用来去查询这个瓦片覆盖了哪些3857的瓦片集合 tile-cover
  • 请求并拼接这些覆盖的瓦片生成一个大的瓦片 maptalks.tileclip layoutTiles
  • 利用当前瓦片的范围去这个拼接的大瓦片上去剪裁我们需要的部分即可
js
function clipImage(image, tiles, clipBBOX) {
    const [x1, y1, x2, y2] = clipBBOX;
    let minx = Infinity,
        miny = Infinity,
        maxx = -Infinity,
        maxy = -Infinity;
    tiles.forEach((tile) => {
        const [xmin, ymin, xmax, ymax] = tile.mbbox;
        minx = Math.min(minx, xmin);
        miny = Math.min(miny, ymin);
        maxx = Math.max(maxx, xmax);
        maxy = Math.max(maxy, ymax);
    });
    const { width, height } = image;
    const ax = (maxx - minx) / width,
        ay = (maxy - miny) / height;
    const dx1 = (x1 - minx) / ax;
    const dx2 = (x2 - minx) / ax;
    const dy1 = height - (y2 - miny) / ay;
    const dy2 = height - (y1 - miny) / ay;
    const canvas = getCanvas();
    resizeCanvas(canvas, TILESIZE, TILESIZE);
    const ctx = getCanvasContext(canvas);
    ctx.drawImage(
        image,
        dx1,
        dy1,
        dx2 - dx1,
        dy2 - dy1,
        0,
        0,
        TILESIZE,
        TILESIZE,
    );
    return canvas.transferToImageBitmap();
}
  • 将剪裁好的图片返回给图层的瓦片请求即可

WARNING

注意剪裁是要用墨卡托坐标,因为拼接的大瓦片是3857的,所以我们还需要把当前瓦片的坐标转成3857的去剪裁

js
//get tile bbox is 4547
const tileBBOX = baseLayer._getTileBBox(tile);
if (!bboxIntersect(MAXBBOX, tileBBOX)) {
    callback(maptalks.getBlankTile());
    return;
}
//transform 4547 to 4326
const bbox = prjBBOX(tileBBOX);
if (!validateBBOX(bbox)) {
    callback(maptalks.getBlankTile());
    return;
}

//tansform 4326 to 3857
const clipBBOX = bboxToMBBOX(bbox);
const zoom = tile.z;
const polygon = bboxToPolygon(bbox);
//cal the bbox cover tiles
const tiles = TileCover.tiles(polygon, {
    min_zoom: zoom,
    max_zoom: zoom,
}).map((tile) => {
    const [x, y, z] = tile;
    //cal tile 3857 bbox
    const mbbox = merc.bbox(x, y, z, false, "900913");
    return {
        mbbox,
        tile,
        x,
        y,
        z,
    };
});
//layout tiles by maptalks.tileclip plugin
tileActor
    .layoutTiles({
        urlTemplate: baseLayer.options.urlTemplate,
        tiles: tiles.map((item) => {
            const { x, y, z } = item;
            return [x, y, z];
        }),
    })
    .then((image) => {
        const cImage = clipImage(image, tiles, clipBBOX);
        tileActor
            .clipTile({
                tile: cImage,
                tileBBOX: tileBBOX,
                projection: baseLayer.getProjection().code,
                tileSize: baseLayer.getTileSize().width,
                maskId,
            })
            .then((image) => {
                callback(image);
            })
            .catch((error) => {
                console.error(error);
            });
    })
    .catch((error) => {
        console.error(error);
    });

完整例子

map sp is epsg4547 demo

maptalks教程 document auto generated by mdpress and vitepress