cesium实现经纬度网格

109 阅读3分钟

一、引言

在地理信息可视化领域,经纬度网格是一种基础且重要的空间参考元素。它通过纬线(东西方向)和经线(南北方向)交织形成网格,帮助用户直观理解地理位置关系。本文档将详细介绍如何使用 Cesium 实现经纬度网格,并解析其核心原理与实现代码。 效果如图: e14973b5-decf-4c64-844d-4a36a1ae969a.png

二、技术基础

1. Cesium 简介

Cesium 是一款开源的 3D 地理信息引擎,支持全球级别的地形、影像和矢量数据可视化,广泛应用于数字地球、GIS 等领域。其基于 WebGL 技术,可在浏览器中实现高性能的 3D 地理可视化效果。

2. 经纬度网格核心概念

  • 经线:连接南北两极的半圆弧线,指示南北方向,经度范围为 -180° 至 180°。
  • 纬线:与赤道平行的圆圈,指示东西方向,纬度范围为 -90° 至 90°。
  • 网格间隔:相邻两条经纬线之间的角度差,决定了网格的密集程度。

三、实现方案

1. 实现思路

Cesium 本身未直接提供经纬度网格组件,但可通过以下方式实现:

  • 使用 Cesium.Primitive 绘制自定义几何体
  • 按指定间隔计算经纬线的坐标点
  • 将坐标点组合成线实体并添加到场景中
  • 配置线条样式(颜色、宽度等)增强可视化效果

2. 实现代码

  • 网格代码
// 绘制经纬网格方法
drawOptimizedLatLonGrid(stepLat = 1, stepLon = 1, minLon, minLat, maxLon, maxLat) {
    const viewer = window.viewer;
    const scene = viewer.scene;
    // 清除之前的网格
    this.clearGrid();
    // 使用Primitive API绘制
    const lonLinePrimitive = new Cesium.Primitive({
        appearance: new Cesium.PolylineMaterialAppearance({
            material: new Cesium.Material({
                fabric: {
                    type: 'Color',
                    uniforms: {
                        color: Cesium.Color.RED
                    }
                }
            })
        }),
        geometryInstances: this.createLongitudeLineInstances(stepLon, minLon, maxLon, minLat, maxLat),
        asynchronous: false,
        show: this.globalControlStatus
    });
    const latLinePrimitive = new Cesium.Primitive({
        appearance: new Cesium.PolylineMaterialAppearance({
            material: new Cesium.Material({
                fabric: {
                    type: 'Color',
                    uniforms: {
                        color: Cesium.Color.RED
                    }
                }
            })
        }),
        geometryInstances: this.createLatitudeLineInstances(stepLat, minLat, maxLat, minLon, maxLon),
        asynchronous: false,
        show: this.globalControlStatus
    });
    const gridPrimitive = new Cesium.Primitive({
        appearance: new Cesium.MaterialAppearance({
            material: new Cesium.Material({
                fabric: {
                    type: 'Color',
                    uniforms: {
                        color: Cesium.Color.WHITE.withAlpha(0.1)
                    }
                }
            }),
            translucent: true,
            closed: true
        }),
        geometryInstances: this.createGridInstances(stepLat, stepLon, minLat, maxLat, minLon, maxLon),
        asynchronous: false,
        show: this.globalControlStatus
    });
    // 存储Primitive以便后续控制
    this.gridPrimitives = {
        lonLines: lonLinePrimitive,
        latLines: latLinePrimitive,
        grid: gridPrimitive
    };
    // 添加到场景
    scene.primitives.add(lonLinePrimitive);
    scene.primitives.add(latLinePrimitive);
    scene.primitives.add(gridPrimitive);
},

// 创建经度线实例
createLongitudeLineInstances(stepLon, minLon, maxLon, minLat, maxLat) {
    const instances = [];

    for (let lon = minLon; lon <= maxLon; lon += stepLon) {
        const positions = [];
        for (let lat = minLat; lat <= maxLat; lat += stepLon) {
            positions.push(Cesium.Cartesian3.fromDegrees(lon, lat, 500));
        }
        instances.push(new Cesium.GeometryInstance({
            geometry: new Cesium.PolylineGeometry({
                positions: positions,
                width: 1,
                vertexFormat: Cesium.PolylineMaterialAppearance.VERTEX_FORMAT
            }),
            id: `lonLine_${lon}`,
            attributes: {
                color: Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.RED)
            }
        }));
    }
    return instances;
},

// 创建纬度线实例
createLatitudeLineInstances(stepLat, minLat, maxLat, minLon, maxLon) {
    const instances = [];

    for (let lat = minLat; lat <= maxLat; lat += stepLat) {
        const positions = [];
        for (let lon = minLon; lon <= maxLon; lon += stepLat) {
            positions.push(Cesium.Cartesian3.fromDegrees(lon, lat, 500));
        }

        instances.push(new Cesium.GeometryInstance({
            geometry: new Cesium.PolylineGeometry({
                positions: positions,
                width: 1,
                vertexFormat: Cesium.PolylineMaterialAppearance.VERTEX_FORMAT
            }),
            id: `latLine_${lat}`,
            attributes: {
                color: Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.RED)
            }
        }));
    }

    return instances;
},
// 创建网格实例
createGridInstances(stepLat, stepLon, minLat, maxLat, minLon, maxLon) {
    const instances = [];
    const defaultColor = Cesium.Color.WHITE.withAlpha(0.1);
    for (let lon = minLon; lon < maxLon; lon += stepLon) {
        for (let lat = minLat; lat < maxLat; lat += stepLat) {
            const west = lon;
            const south = lat;
            const east = lon + stepLon;
            const north = lat + stepLat;

            const rectangle = Cesium.Rectangle.fromDegrees(west, south, east, north);
            const id = `grid_${west}_${south}_${east}_${north}`;

            instances.push(new Cesium.GeometryInstance({
                geometry: new Cesium.RectangleGeometry({
                    rectangle: rectangle,
                    height: 500,
                    vertexFormat: Cesium.MaterialAppearance.MaterialSupport.TEXTURED.vertexFormat
                }),
                id: id,
                attributes: {
                    color: Cesium.ColorGeometryInstanceAttribute.fromColor(defaultColor)
                }
            }));
        }
    }
    return instances;
},
// 清除网格
clearGrid() {
    const viewer = window.viewer;
    const scene = viewer.scene;
    if (this.gridPrimitives) {
        scene.primitives.remove(this.gridPrimitives.lonLines);
        scene.primitives.remove(this.gridPrimitives.latLines);
        scene.primitives.remove(this.gridPrimitives.grid);
        this.gridPrimitives = null;
    }
    if (this.gridClickHandler) {
        this.gridClickHandler.destroy();
        this.gridClickHandler = null;
    }
},

// 调用
this.drawOptimizedLatLonGrid(1, 1, -180, -90, 180, 90);
  • 网格点击查看详情代码
// 地图点击事件
let handler = new Cesium.ScreenSpaceEventHandler(
      window.viewer.scene.canvas
    );
handler.setInputAction((movement) => {
  let pick = window.dataBoardViewer.scene.pick(movement.position);
    if(pick.id && pick.id.startsWith('grid_')){
    this.openInfoBoxGrid(pick.id, movement.position, cartesian)
  } else {
    this.visibleGrid = false;
  }
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
// 弹窗HTML
<div v-show="visibleGrid" class="infoBox" id="popup-grid">
    <div>经度范围:{{ gridInfo.west }}~{{ gridInfo.east }}</div>
    <div>纬度范围:{{ gridInfo.south }}~{{ gridInfo.north }}</div>
</div>
// 经纬网格弹窗
openInfoBoxGrid(pickedObject, position, cartesian, type) {
  if (
    !pickedObject ||
    !pickedObject.startsWith('grid_')
  ) {
    this.visibleGrid = false;
    return;
  }
  // 2. 获取数据
  const parts = pickedObject.split('_');
  const west = parseFloat(parts[1]);
  const south = parseFloat(parts[2]);
  const east = parseFloat(parts[3]);
  const north = parseFloat(parts[4]);
  this.gridInfo = {
    west: west,
    south: south,
    east: east,
    north: north,
  }
  // 3. 更新弹窗内容
  this.visibleGrid = true;
  window.viewer.scene.postRender.addEventListener(() => {
    let windowPosition =
      window.viewer.scene.cartesianToCanvasCoordinates(cartesian);
    // 数值是样式中定义的宽高
    if (windowPosition == undefined) return;
    // 4. 计算弹窗位置
    const popup = document.getElementById("popup-grid");
    if (!popup) {
      console.error("弹窗DOM元素未找到!");
      return;
    }
    // 5. 防溢出处理
    const containerRect = window.dataBoardViewer.canvas.getBoundingClientRect();
    const popupWidth = popup.offsetWidth;
    const popupHeight = popup.offsetHeight;
    let left = windowPosition.x;
    let top = windowPosition.y;
    // 防止右侧超出视口
    if (left + popupWidth > window.innerWidth) {
      left = window.innerWidth - popupWidth - 10;
    }
    // 防止顶部超出视口
    if (top < 0) {
      top = 10;
    }
    // 6. 更新弹窗位置和显示状态
    popup.style.left = `${left}px`;
    popup.style.top = `${top}px`;
  });
},
  • 控制网格显隐代码
globalGridChange: function (val) {
  // 先关闭弹窗
  this.visibleGrid = false
  if (this.gridPrimitives) {
    this.gridPrimitives.lonLines.show = val;
    this.gridPrimitives.latLines.show = val;
    this.gridPrimitives.grid.show = val;
  }
},

四、总结

本文档介绍了基于 Cesium 实现经纬度网格的方案,通过自定义几何体的方式,我们可以灵活控制网格的密度、样式和显示效果。该实现方法不仅适用于经纬度网格,也可扩展到其他类型的地理参考线网可视化。