一、引言
在地理信息可视化领域,经纬度网格是一种基础且重要的空间参考元素。它通过纬线(东西方向)和经线(南北方向)交织形成网格,帮助用户直观理解地理位置关系。本文档将详细介绍如何使用 Cesium 实现经纬度网格,并解析其核心原理与实现代码。
效果如图:
二、技术基础
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 实现经纬度网格的方案,通过自定义几何体的方式,我们可以灵活控制网格的密度、样式和显示效果。该实现方法不仅适用于经纬度网格,也可扩展到其他类型的地理参考线网可视化。