Bootstrap

leaflet坐标系的设置和切换

leaflet坐标系的设置

提示:


前言

本篇文章主要是leaflet的应用讲解,最好先对leaflet和坐标系有一定的了解。
坐标系相关可以参考我之前写的文章 https://blog.csdn.net/weixin_42066016/article/details/120371884?spm=1001.2014.3001.5501


一、如何设置leaflet坐标系?

这次项目接到的任务是要加载和展示墨卡托和兰伯特坐标系的数据。首先要做的就是能正常加载这两个坐标系数据。首先翻翻http://epsg.io,最后确定墨卡托用最常见的3857,兰伯特用3415(一个中国南海的坐标系)。

ps:最好找一个懂GIS的人来确定坐标系,或者让甲方客户甚至的领导决定,要不然自己挖的坑只能自己填。

坐标系确定后,和所有涉及GIS,RS,地图相关的人员确认和统一,不然各个坐标系的数据汇总收集管理应用将是个大问题。

这里我们不做leaflet的基础培训,如果对leaflet还不了解话,建议先去学习一下基础概念和操作后再看本篇文章。

Leaflet在创建地图对象时可以设置坐标系参数crs,我们看下文档中是怎么描述这个参数的。
在这里插入图片描述

作者也是在这里进行了吐槽,“不知道啥意思,你就别动它”

可以看到leaflet给的默认值是EPSG3857,很常用不在赘述。网络上的很多地图服务都是3857坐标系的,例如天地图,谷歌地图,高德地图等。这样其实我的第一个目标就已经完成了,剩下的就是设置3415坐标系。那么我们直接设置crs为L.CRS.EPSG3415是不是就可以呢?当然不行,因为leaflet里面内置了3857和4326,而其他的坐标系都需要我们自定义。

这里我们需要引入leaflet插件Proj4Leaflet,来实现功能,具体步骤如下。
第一步,下载插件引入。
第二步,在http://epsg.io中搜索要加载的坐标系。
第三步,把对应的参数复制粘贴出来,填入对应属性。如下图(Export选择PROJ.4,截图没有截全)。

在这里插入图片描述
有同学要问resolutions是什么?是比例尺参数,不懂的话可以直接复制粘贴就好。

L.CRS.EPSG3415 = new L.Proj.CRS('EPSG:3415',
    '+proj=lcc +lat_1=18 +lat_2=24 +lat_0=21 +lon_0=114 +x_0=500000 +y_0=500000 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs',
    {
        resolutions: (function () {
            var level = 17
            var res = []
            res[0] = Math.pow(2, level)
            for (var i = 1; i < level - 5; i++) {
                res[i] = Math.pow(2, (level - i))
            }
            return res
        }()),
        origin: [2752609.29, -11909708.50],
        bounds: L.bounds([14068705.421709407, -29736152.4826897], [-16397006.661909804, 30735400.422382265])
    })

第四步,创建map时引用创建的坐标系。

  this.map = L.map(divID, {
        crs: L.CRS.EPSG3415,
        center: options.center, // 地图中心
        zoom: options.zoom, //缩放比列
        zoomControl: false, //禁用 + - 按钮
        doubleClickZoom: false, // 禁用双击放大
        attributionControl: false, // 移除右下角leaflet标识
    })

到此地图初始化工作完毕。Leaflet方便的一点是,向地图添加矢量数据(例如marker,Polyline等),坐标如果是4326的经纬度,会自动转换坐标系。
在这里插入图片描述
Leaflet中添加天地图和矢量线边界(3857)
在这里插入图片描述
Leaflet中添加矢量线边界(3415)

到此leaflet坐标系设置完成,其实代码工作量很少,主要是涉及坐标系调研和选取是十分重要的,由于互联网上没有相关的3451的底图数据,那么只能自己处理一些数据然后使用geoserver发布出来使用了。

扩充:图片的数据叠加坐标系问题

坐标系转换会出现各种奇奇怪怪的问题,这个只是我遇到的其中之一,拿出来和大家分享一下。
我们的项目需要叠加卫星产品到地图上,如图。
在这里插入图片描述
这个图片数据是遥感小妹用算法处理好,然后提供给我图片和图片的bounds,然后我这边使用ImageOverlay就可以贴到地图上。

/*
    添加图片图层
    imageUrl:需要加载的图片url
    imageBounds:图片的外边界 例如:[[40.712216, -74.22655], [40.773941, -74.12544]];
    other:其他属性例如 透明度等
*/
  layer = L.imageOverlay(
            imageUrl,
            imageBounds,
            { other, pane: 'imageOverlayPane' }
        ).addTo(this.map);

例如一些软件的天气云图就是通过这种方法实现的,这些数据都是遥感算法人员提供的。

这种数据如果要用3415加载,光给两个bounds坐标和3857坐标系数据可是不行的,因为图片数据没有地理信息,无法进行坐标系转换的工作。带地理信息的图片数据叫tif栅格数据,这种数据很大,就我们的项目而言,同样的图片数据png为2mb,而tif为10mb,这对浏览器加载是十分不友好的。

于是在数据生产时,生成了两份,一份3857(上图),一份3415。遥感工程师说bounds没变,我开开心心去加的时候发生了问题。下图。

在这里插入图片描述
怎么会歪了呢,我还再三和遥感小妹确认,甚至要来了原始tif数据,自己到GIS软件处理。后来发现是形变导致的bounds也发生了改变。

在这里插入图片描述
在这里插入图片描述
Leaflet添加图片图层需要添加[X1,Y1]和[[X2,Y2],根据上面两张图对比我们发现投影转换后[X1,Y1]和[[X2,Y2]已经变成了[X3,Y3]和[[X4,Y4],所以贴上去会产生偏移和形变。我想到的解决办法是通过打点采样四条边,找到最大最小的X和Y,然后组合成[X1,Y1]和[[X2,Y2],虽然还是会有误差(可以加采样点减少误差),但是在大范围内几乎看不出来明显偏移和形变。

/*
    添加图片图层
    id:图层id,用于清除。
    imageUrl:需要加载的图片url
    imageBounds:图片的外边界 例如:[[40.712216, -74.22655], [40.773941, -74.12544]];
    other:其他属性例如 透明度等
*/
LMap.prototype.addImageLayer = function (id, imageUrl, imageBounds, other) {
    var layer = null;
    if (this.epsg == L.CRS.EPSG3857 || !this.epsg) {
        this.imageLayers.forEach((e) => {
            if (e.id == id)
                return false;
        })
        layer = L.imageOverlay(
            imageUrl,
            imageBounds,
            { other, pane: 'imageOverlayPane' }
        ).addTo(this.map);
    } else {
        let bounds = [...imageBounds[0], ...imageBounds[1]]
        let coordinatesX = [];
        let coordinatesY = [];
        let valueX = bounds[1] - bounds[3]
        let valueY = bounds[0] - bounds[2]
        //插值计算bounds
        for (var i = 0; i < 4; i++) {
            for (var j = 0; j <= 10; j++) {
                let point = []
                if (i == 0) point = proj4("EPSG:4326", this.epsg.code, [bounds[1] - valueX * j * 0.1, bounds[0]]);
                else if (i == 1) point = proj4("EPSG:4326", this.epsg.code, [bounds[1], bounds[0] - valueY * j * 0.1]);
                else if (i == 2) point = proj4("EPSG:4326", this.epsg.code, [bounds[3] + valueX * j * 0.1, bounds[2]]);
                else if (i == 3) point = proj4("EPSG:4326", this.epsg.code, [bounds[3], bounds[2] + valueY * j * 0.1]);
                coordinatesX.push(point[0]);
                coordinatesY.push(point[1]);
            }
        }
        //排序
        coordinatesX.sort(function (a, b) {
            return b - a;
        })
        coordinatesY.sort(function (a, b) {
            return b - a;
        })
        layer = L.Proj.imageOverlay(
            imageUrl,
            L.bounds(
                //-4655709.0378614282,5687384.6802430525
                [coordinatesX[coordinatesX.length - 1], coordinatesY[0]],
                // 3571797.5203373069,-1871514.4962498695
                [coordinatesX[0], coordinatesY[coordinatesY.length - 1]]
            )
            , { ...other, pane: 'imageOverlayPane' }
        ).addTo(this.map);
    }
    this.imageLayers.push({ id: id, layer: layer })
}

在这里插入图片描述

二、如何切换leaflet坐标系

Leaflet中并没有切换坐标系的方法,我想到的方法是销毁现在的地图,记录销毁前的地图层级(zoom),地图中心点,然后重新添加销毁前的数据(marker,Polyline等)和设置zoom,center等参数达到“切换的效果”。

/*
    初始化地图
    divID:地图所在div的id。
*/
LMap.prototype.initMap = function (divID, options = {}, mouseEvent) {
    options.zoom = options.zoom ? options.zoom : 6;
    options.epsg = options.epsg ? options.epsg : L.CRS.EPSG3857;
    options.center = options.center ? options.center : [39.905033413167, 116.40191241];
    this.crs = options.epsg;
    this.map = L.map(divID, {
        crs: options.epsg,
        center: options.center, // 地图中心
        zoom: options.zoom, //缩放比列
        zoomControl: false, //禁用 + - 按钮
        doubleClickZoom: false, // 禁用双击放大
        attributionControl: false, // 移除右下角leaflet标识
    })
}
/*
    切换地图坐标系,切换后,底图数据,image数据清除需要重新添加
    crs:坐标系参数,具体参考crs变量
    isSaveData:是否保留添加的矢量数据,ture保留,默认false
*/
LMap.prototype.changeCrs = function (crs, isSaveData) {
    let options = {
        center: this.map.getCenter(),
        zoom: this.map.getZoom(),
        epsg: L.CRS[crs]
    }
    this.map.remove();
    this.initMap(this.divID, options, this.mouseEvent)
    if (isSaveData) {
        this.vectorLayers.forEach(i => {
            i.addTo(this.map);
        })
    } else {
        this.vectorLayers = []
    }
    this.imageLayers = []
    this.baseLayer = []
    this.wmsLayers = []
}

LMap是我自己面向对象封装的类。imageLayers ,baseLayer ,wmsLayers 用于存储各种图层数据。方便查找和删除。vectorLayers 存储矢量数据。

总结

参考了很多其他大神的文章,写完忘记地址了,如果大神看到了,私信我一下,我会把文章链接挂在下面。相关主要代码都已经展示。后面会把代码整成一个小demo放出来,啥时候嘛,最近有点懒。。

;