vue+leaflet笔记之地图网格

vue+leaflet笔记之地图网格

本文介绍了Web端使用Leaflet开发库显示地图经纬网和标准图幅网格的方法 (底图来源:天地图), 地图格网是由间隔均匀的横向线和纵向线组成的网络,用于在地图上识别各个位置。 经纬网通过在地图上描绘纬度和经度格网,用于表示地图的地理坐标;标准图幅网格通过在地图上展示不同比例尺下的网格图幅号与点击查询地图范围,用于描绘地图的位置、比例尺等信息,示例效果如下图所示。


开发环境

Vue开发库:3.2.37 & Leaflet开发库:1.9.3

主要插件:turf


使用教程

实现方式

首先,获取到当前地图map,通过map.getBounds() map.getZoom()获取到当前地图范围和当前地图缩放级别 ,利用地图范围和缩放级别按照对应的规则进行计算,生成地图经纬网或标准图幅网格要素,然后添加到图层组(地图)中。

核心代码

经纬网

经纬网是由表示地球东西位置的经线和表示地球南北位置的子午线构成,由于大家日常接触较多,这里不再赘述。

效果图
核心代码
// 获取当前地图缩放级别
let zoom = map.getZoom(); 
if (zoom > 2 || zoom < 20) {
    let bounds = map.getBounds(); // 获取到当前地图范围
    let north = bounds.getNorth(); // 当前视口最北点经度
    let east = bounds.getEast(); // 当前视口最东点纬度
    // 经线网格
    for (let index = -180; index <= 360; index += degree) {
        // 判断是否在当前视野内
        if (bounds.contains([north, index])) {
            // 绘制经线
            let lonLine = L.polyline([[-90, index], [90, index],],
                                     { weight: 1, dashArray: '10, 5', color: gridLineLatLng.color } // 线样式
                                    );
            // 添加到图层组中
            gridLineLatLng.currentFeatureLayerGroup.addLayer(lonLine);
            // 文字标注
            let text = index.toFixed(1) + "°";
            // 动态计算小数位数
            if (zoom > 10) {
                text = index.toFixed((zoom - 8) / 2) + "°";
            }
            let divIcon = L.divIcon({
                html: `<span style="white-space: nowrap;color:#fff;font-size:14px;font-family:Times New Roman;font-weight:700;text-shadow: 0 0 5px #ff1200, 0 0 10px #ff1200;">${text}</span>`,
                className: 'grid-text-icon', // 取消白色的方形显示
                iconAnchor: [14, -5],
            });
            let textMarker = L.marker([north, index], { icon: divIcon });
            gridLineLatLng.currentFeatureLayerGroup.addLayer(textMarker);
        }
    }
    if (degree > 90) degree = 90;
    // 纬线网格处理代码
    
}
// 注册地图鼠标移动事件,动态生成网格
gridLineLatLng.currentMap.on('mousemove', gridLineLatLng.mousemove);
标准图幅网格

我国把 1:1 万、1:2.5 万、1:5 万、1:10 万、1:25 万、1:50 万、1:100 万 7 种比例尺作为国家基本地图的比例尺系列。根据国家标准GB/13989-92 《国家基本比例尺地形图分幅和编号》规定,我国基本比例尺地形图均1:100 万地形图为基础,按规定的经差和纬差划分图幅。下表为地形图的经纬差、行列数及图幅数。

由于南北两半球的经度相同,规定在南半球的图号前加一个 S,北半球的图号前不加任何符号。如南、北半球经纬度度数相同的情况下北半球图幅号为 F50B001001 ,而南半球则为SF50B001001。比例尺代码含义如下图所示。

效果图
核心代码

计算方法参考leaflet 叠加新标准分幅网格

let features = []; // 存储生成的面要素
let coordinates = [] // 存储生成的面要素坐标对
// 计算标准分幅网格行列范围
const millionRowCode = 'ABCDEFGHIJKLMNOPQRSTUV';
const col0 = parseInt((x0 - GridX0) / lngDiff);
const col1 = parseInt((x1 - GridX0) / lngDiff);
const row0 = parseInt((y0 - GridY0) / latDiff);
const row1 = parseInt((y1 - GridY0) / latDiff);
for (let row = row0; row <= row1; row++) {
    let gy0 = GridY0 + row * latDiff;
    let gy1 = gy0 + latDiff;
    let gcy = (gy0 + gy1) * 0.5;    // 分幅中心点 y 坐标
    let millionRow = parseInt((gy0 - 0) / 4); // 1:100分幅行号
    let Hemisphere = '';   // 北半球标志
    if (millionRow < 0) {
        millionRow = -1 - millionRow;
        Hemisphere = 'S'; // 南半球标志
    }
    for (let col = col0; col <= col1; col++) {
        let gx0 = GridX0 + col * lngDiff;
        let gx1 = gx0 + lngDiff;
        let gcx = (gx0 + gx1) * 0.5;    // 分幅中心点 x 坐标
        let millionCol = parseInt((gcx - GridX0) / 6) + 1;  // 1:100分幅列号(从1开始)
        coordinates = [[[gx0, gy0], [gx1, gy0], [gx1, gy1], [gx0, gy1], [gx0, gy0]]];
        millionCol = (millionCol < 10) ? ('0' + millionCol) : millionCol;
        let gridID = Hemisphere + millionRowCode[millionRow] + millionCol;
        if (scaleCode != '') {
            // 计算当前分幅在 1:100万 分幅内的行列号(注意,这里行列号从左向右,从北向南,从1开始编号)
            let colID = parseInt((fractional((gcx - GridX0) / 6) * 6) / lngDiff) + 1;
            let rowID = parseInt((fractional((GridY1 - gcy) / 4) * 4) / latDiff) + 1;
            gridID += scaleCode + formatInt(rowID, 3) + formatInt(colID, 3);
        }
        // 生成矢量要素(几何信息+属性信息)
        let feature = {
            type: "Feature",
            geometry: {
                type: "Polygon",
                coordinates: coordinates
            },
            properties: {
                ID: gridID,
                extend: '西:' + gx0 + ' 东:' + gx1 + ' 南:' + gy0 + ' 北:' + gy1
            }
        };
        features.push(feature);
    }
}

详细源码(Vue3)

主文件index.vue

<template>
  <div class="app-contain">
    <!-- leaflet 地图容器 -->
    <div id="myMap"></div>
    <div class="controls">
      <el-button color="#626aef" @click="mapGridLonLat">经纬网</el-button>
      <el-button color="#626aef" @click="mapGridStandard">标准图幅</el-button>
      <el-button color="#626aef" @click="clearMapGrid()">清空</el-button>
    </div>
  </div>
</template>

<script setup>
import { gridLineLatLng, gridLineStandard } from './store/mapGrids.js'
// 引入样式
import L from 'leaflet';
import 'leaflet/dist/leaflet.css'
import { onMounted } from 'vue'

let map = null;
let gridLineLayer = null;
// 天地图TK
let tdtKey = 'YOURS_TK'
const initMap = () => {
  // 天地图-矢量注记服务
  const vector_cva =
    new L.TileLayer(`http://t0.tianditu.gov.cn/cva_c/wmts?layer=cva&style=default&tilematrixset=c&Service=WMTS&Request=GetTile&Version=1.0.0&Format=tiles&TileMatrix={z}&TileCol={x}&TileRow={y}&tk=${tdtKey}`,
      {
        tileSize: 512,
        noWrap: true,
        bounds: [[-90, -180], [90, 180]]
      })
  // 天地图-矢量地图服务
  const vector_vec =
    new L.TileLayer(`http://t0.tianditu.com/vec_c/wmts?layer=vec&style=default&tilematrixset=c&Service=WMTS&Request=GetTile&Version=1.0.0&Format=tiles&TileMatrix={z}&TileCol={x}&TileRow={y}&tk=${tdtKey}`,
      {
        tileSize: 512,
        noWrap: true,
        bounds: [[-90, -180], [90, 180]]
      })

  const layers = L.layerGroup([vector_vec, vector_cva])

  map = L.map('myMap', {  //需绑定地图容器div的id
    attributionControl: false,
    zoomControl: true, // 显示缩放控件
    center: [39.56, 116.20], //初始地图中心
    crs: L.CRS.EPSG4326,
    // 最小显示等级
    minZoom: 1,
    // 最大显示等级
    maxZoom: 18,
    scrollWheelZoom: true, //默认开启鼠标滚轮缩放
    // 限制显示地理范围
    maxBounds: L.latLngBounds(L.latLng(-90, -180), L.latLng(90, 180)),
    layers: [layers],// 图层
  }).setView([28.907459, 120.003576], 6)

  /** 网格生成 */

  // 创建图层组
  gridLineLayer = L.featureGroup().addTo(map).bringToFront();
}

// 经纬网
const mapGridLonLat = () => {
  // 若存在,则移除标准图幅网格
  if (gridLineStandard.currentMap != null) {
    gridLineStandard.destory()
  }
  gridLineLatLng.currentMap = map;
  gridLineLatLng.currentFeatureLayerGroup = gridLineLayer
  // 间隔度数
  gridLineLatLng.degree = 1;
  // 网格颜色
  gridLineLatLng.color = '#6666ff'
  gridLineLatLng.init()
}
// 标准图幅网格
const mapGridStandard = () => {
  // 若存在,则移除标准图幅网格
  if (gridLineStandard.currentMap != null) {
    gridLineLatLng.destory()
  }
  gridLineStandard.currentMap = map;
  gridLineStandard.currentFeatureLayerGroup = gridLineLayer
  gridLineStandard.init()
}
// 网格清空
const clearMapGrid = () => {
  // 若存在,则移除标准图幅网格
  if (gridLineStandard.currentMap != null) {
    gridLineLatLng.destory()
  }
  // 若存在,则移除标准图幅网格
  if (gridLineStandard.currentMap != null) {
    gridLineStandard.destory()
  }
}

onMounted(() => {
  initMap()
})
</script>

<style scoped>
#myMap {
  width: 94vw;
  height: 96vh;
}
.grid-text-icon {
  font-size: 16px;
}
.controls {
  position: absolute;
  top: 0px;
  left: 200px;
  padding: 15px;
  z-index: 1000;
}
</style>

mapGrids.js

/** 
 * 地图网格工具js
 * 
 * @author fungis@163.com
 * @date 2023-05-18
 */

// 本地资源数据
import { makeStandardMapGrids } from './standardMapGrids.js'
// 引入turf
import * as turf from '@turf/turf'

export {
    gridLineLatLng, gridLineStandard
}

/** 地图经纬网格网 */
let gridLineLatLng = {
    currentMap: null,
    currentFeatureLayerGroup: null,
    color: "blue",
    degree: 2,
    init: function () {
        // 清理图层
        gridLineLatLng.currentFeatureLayerGroup.clearLayers();
        // 经纬度间隔
        let map = gridLineLatLng.currentMap;
        let degree = gridLineLatLng.degree;
        // 小于0.01时按0.01计算(数值越小,计算事件越长)
        if (degree < 0.01) {
            degree = 0.01
        }
        let zoom = map.getZoom(); // 获取当前地图缩放级别
        if (zoom > 2 || zoom < 20) {
            let bounds = map.getBounds(); // 获取到当前地图范围
            let north = bounds.getNorth(); // 当前视口最北点经度
            let east = bounds.getEast(); // 当前视口最东点纬度
            // 经线网格
            for (let index = -180; index <= 360; index += degree) {
                // 判断是否在当前视野内
                if (bounds.contains([north, index])) {
                    // 绘制经线
                    let lonLine = L.polyline([[-90, index], [90, index],],
                        { weight: 1, dashArray: '10, 5', color: gridLineLatLng.color } // 线样式
                    );
                    // 添加到图层组中
                    gridLineLatLng.currentFeatureLayerGroup.addLayer(lonLine);
                    // 文字标注
                    let text = index.toFixed(1) + "°";
                    // 动态计算小数位数
                    if (zoom > 10) {
                        text = index.toFixed((zoom - 8) / 2) + "°";
                    }
                    let divIcon = L.divIcon({
                        html: `<span style="white-space: nowrap;color:#fff;font-size:14px;font-family:Times New Roman;font-weight:700;text-shadow: 0 0 5px #ff1200, 0 0 10px #ff1200;">${text}</span>`,
                        className: 'grid-text-icon', // 取消白色的方形显示
                        iconAnchor: [14, -5],
                    });
                    let textMarker = L.marker([north, index], { icon: divIcon });
                    gridLineLatLng.currentFeatureLayerGroup.addLayer(textMarker);
                }
            }
            if (degree > 90) degree = 90;
            // 纬线网格
            for (let index = -90; index <= 90; index += degree) {
                if (bounds.contains([index, east])) {
                    let lonLine = L.polyline([[index, -180], [index, 360],],
                        { weight: 1, dashArray: '10, 5', color: gridLineLatLng.color } // 线样式
                    );
                    gridLineLatLng.currentFeatureLayerGroup.addLayer(lonLine);
                    // 标注
                    let text = index.toFixed(1) + "°";
                    if (zoom > 10) {
                        text = index.toFixed((zoom - 8) / 2) + "°";
                    }
                    let divIcon = L.divIcon({
                        html: `<span style="white-space: nowrap;color:#fff;font-size:14px;font-family:Times New Roman;font-weight:700;text-shadow: 0 0 5px #ff1200, 0 0 10px #ff1200;">${text}</span>`,
                        className: 'grid-text-icon', // 取消白色的方形显示
                        iconAnchor: [(text.length + 1) * 6, 0],
                    });
                    let textMarker = L.marker([index, east], { icon: divIcon });
                    gridLineLatLng.currentFeatureLayerGroup.addLayer(textMarker);
                }
            }
        }
        // 注册地图鼠标移动事件,动态生成网格
        gridLineLatLng.currentMap.on('mousemove', gridLineLatLng.mousemove);
    },
    mousemove: function () {
        gridLineLatLng.init();
    },
    destory: function () {
        // 移除地图事件
        gridLineLatLng.currentMap.off('mousemove', gridLineLatLng.mousemove);
        // 清理图层
        gridLineLatLng.currentFeatureLayerGroup.clearLayers();
    }
}

/** 地图标准图幅格网 */
let gridLineStandard = {
    currentMap: null,
    currentFeatureLayerGroup: null,
    color: "red",
    init: function () {
        // 清理图层
        gridLineStandard.currentFeatureLayerGroup.clearLayers();
        // 经纬度间隔
        let map = gridLineStandard.currentMap;
        // 重新添加网格图层
        let gridLineLayer = gridLineStandard.currentFeatureLayerGroup;
        // 生成网格
        let bounds = map.getBounds();
        const zoom = map.getZoom();
        let scale = 1000000;
        if (zoom > 11) {
            scale = 5000;
        } else if (zoom > 10) {
            scale = 10000;
        } else if (zoom > 9) {
            scale = 25000;
        } else if (zoom > 8) {
            scale = 50000;
        } else if (zoom > 7) {
            scale = 100000;
        } else if (zoom > 6) {
            scale = 250000;
        } else if (zoom > 4) {
            scale = 500000;
        }
        let grids = makeStandardMapGrids(bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth(), scale);
        if (grids == null) {
            return;
        }
        let lineLayer = L.geoJSON(grids, {
            style: function (feature) {
                return {
                    color: gridLineStandard.color,
                    weight: 1,
                    fillColor: 'green',
                    dashArray: '10, 5',
                    fillOpacity: 0
                };
            }, onEachFeature(feature, layer) {
                // 显示文字
                let content = "图:" + feature.properties.ID + '<br>'
                // marker的icon文字
                let myIcon = L.divIcon({
                    html: `<div style="white-space: nowrap;color:red;">${content}</div>`,
                    iconAnchor: [50, 0],
                    className: 'my-div-icon',
                    iconSize: 120
                });
                // 中心点位
                let centerPoint = turf.center(feature);
                let featureCenter = L.latLng(centerPoint.geometry.coordinates[1], centerPoint.geometry.coordinates[0]);
                gridLineLayer.addLayer(L.marker(featureCenter, { icon: myIcon }));
            }
        }).bindPopup(function (layer) {
            return "图幅:" + layer.feature.properties.ID + '<br>' + layer.feature.properties.extend;
        });

        gridLineLayer.addLayer(lineLayer);
        gridLineStandard.currentMap.on('mousemove', gridLineStandard.mousemove);
    },
    mousemove: function () {
        gridLineStandard.init();
    },
    destory: function () {
        // 移除地图事件
        gridLineStandard.currentMap.off('mousemove', gridLineStandard.mousemove);
        // 清理图层
        gridLineStandard.currentFeatureLayerGroup.clearLayers();
    }
}

standardMapGrids.js

/** 
 * 地图标准图幅计算要素生成工具js
 * https://www.cnblogs.com/oloroso/p/14129066.html
 * 
 * @author ymwh@foxmail.com
 * @date 2020-12-13
 */
// 获取小数部分
const fractional = function (x) {
    x = Math.abs(x);
    return x - Math.floor(x);
}
const formatInt = function (x, len) {
    let result = '' + x;
    len = len - result.length;
    while (len > 0) {
        result = '0' + result;
        len--;
    }
    return result;
}

/**
 * 创建标准分幅网格
 * @param west,south,east,north 传入要创建的标准分幅网格的经纬度范围
 * @param scalem 表示比例尺的分母(例如 10000 表示 1:1万)
 * @returns 返回一个 geojson 对象
 * @author solym ymwh@foxmail.com 2020年12月13日
 */
export function makeStandardMapGrids(west, south, east, north, scalem) {
    let lngDiff = 0;
    let latDiff = 0;
    let scaleCode = '';
    switch (scalem) {
        case 1000000:
            lngDiff = 6;
            latDiff = 4;
            break;
        case 500000:
            lngDiff = 3;
            latDiff = 2;
            scaleCode = 'B';
            break;
        case 250000:
            lngDiff = 1.5;
            latDiff = 1;
            scaleCode = 'C';
            break;
        case 100000:
            lngDiff = 0.5;
            latDiff = 1 / 3;
            scaleCode = 'D';
            break;
        case 50000:
            lngDiff = 0.25;
            latDiff = 1 / 6;
            scaleCode = 'E';
            break;
        case 25000:
            lngDiff = 0.125;
            latDiff = 1 / 12;
            scaleCode = 'F';
            break;
        case 10000:
            lngDiff = 0.0625;
            latDiff = 1 / 24;
            scaleCode = 'G';
            break;
        case 5000:
            lngDiff = 0.03125;
            latDiff = 1 / 48;
            scaleCode = 'H';
            break;
        default:
            return null;
    }
    const GridX0 = -180;
    const GridX1 = 180;
    const GridY0 = -88;
    const GridY1 = 88;
    let x0 = Math.max(GridX0, west);
    let y0 = Math.max(GridY0, south);
    let x1 = Math.min(GridX1, east);
    let y1 = Math.min(GridY1, north);
    if (((x1 - x0) < lngDiff) || ((y1 - y0) < latDiff)) {
        return null;
    }

    let features = []; // 存储生成的面要素
    let coordinates = [] // 存储生成的面要素坐标对
    // 计算标准分幅网格行列范围
    const col0 = parseInt((x0 - GridX0) / lngDiff);
    const col1 = parseInt((x1 - GridX0) / lngDiff);
    const row0 = parseInt((y0 - GridY0) / latDiff);
    const row1 = parseInt((y1 - GridY0) / latDiff);
    const millionRowCode = 'ABCDEFGHIJKLMNOPQRSTUV';
    for (let row = row0; row <= row1; row++) {
        let gy0 = GridY0 + row * latDiff;
        let gy1 = gy0 + latDiff;
        let gcy = (gy0 + gy1) * 0.5;    // 分幅中心点 y 坐标
        let millionRow = parseInt((gy0 - 0) / 4); // 1:100分幅行号
        let Hemisphere = '';   // 北半球标志
        if (millionRow < 0) {
            millionRow = -1 - millionRow;
            Hemisphere = 'S'; // 南半球标志
        }
        for (let col = col0; col <= col1; col++) {
            let gx0 = GridX0 + col * lngDiff;
            let gx1 = gx0 + lngDiff;
            let gcx = (gx0 + gx1) * 0.5;    // 分幅中心点 x 坐标
            let millionCol = parseInt((gcx - GridX0) / 6) + 1;  // 1:100分幅列号(从1开始)
            coordinates = [[[gx0, gy0], [gx1, gy0], [gx1, gy1], [gx0, gy1], [gx0, gy0]]];
            millionCol = (millionCol < 10) ? ('0' + millionCol) : millionCol;
            let gridID = Hemisphere + millionRowCode[millionRow] + millionCol;
            if (scaleCode != '') {
                // 计算当前分幅在 1:100万 分幅内的行列号(注意,这里行列号从左向右,从北向南,从1开始编号)
                let colID = parseInt((fractional((gcx - GridX0) / 6) * 6) / lngDiff) + 1;
                let rowID = parseInt((fractional((GridY1 - gcy) / 4) * 4) / latDiff) + 1;
                gridID += scaleCode + formatInt(rowID, 3) + formatInt(colID, 3);
            }
            // 生成矢量要素(几何信息+属性信息)
            let feature = {
                type: "Feature",
                geometry: {
                    type: "Polygon",
                    coordinates: coordinates
                },
                properties: {
                    ID: gridID,
                    extend: '西:' + gx0 + ' 东:' + gx1 + ' 南:' + gy0 + ' 北:' + gy1
                }
            };
            features.push(feature);
        }
    }
    return {
        type: "FeatureCollection",
        features: features
    };
}
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Vue Leaflet 是一种结合了Vue框架和Leaflet库的前端技术,用于展示和操作天地图。天地图是一种具有高清影像和矢量数据的地图服务,提供了丰富的地理信息资源和功能,如地图展示、地图操作、定位导航等。 Vue Leaflet 可以通过调用天地图的API接口,获取并展示天地图的各类地理信息。通过Vue的组件化开发方式,可以方便地在Vue项目中使用这些地理信息,实现自定义的地图功能。例如,在Vue Leaflet 中可以实现地图、标记点、线段、面等地理要素的显示和编辑。 Vue Leaflet 提供了一套方便易用的API和组件,可以轻松地在Vue项目中集成和使用天地图。比如,可以使用Vue Leaflet 提供的地图组件将天地图展示在网页中,可以使用它提供的标记点组件在地图上添加标记,可以使用它提供的工具条组件进行地图的操作和导航等。 使用Vue Leaflet 可以有效地提高开发效率和用户体验。通过其简洁的API和灵活的组件,开发人员可以快速地实现各种地图需求,如显示地图、标记地点、展示线段等。并且,Vue Leaflet 结合了Vue框架的优势,可以更好地组织和管理地图相关的逻辑代码,使开发工作更加方便和高效。 总之,Vue Leaflet 是一种方便、灵活和高效的前端技术,用于展示和操作天地图。它通过结合Vue框架和Leaflet库,提供了一套方便易用的API和组件,帮助开发人员快速实现各种地图需求,提高开发效率和用户体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值