第12章 - 空间分析与测量
第12章:空间分析与测量
12.1 距离测量
12.1.1 两点间距离
// 计算两点间直线距离
function calculateDistance(point1, point2) {
const cartesian1 = Cesium.Cartesian3.fromDegrees(point1.lon, point1.lat, point1.height || 0);
const cartesian2 = Cesium.Cartesian3.fromDegrees(point2.lon, point2.lat, point2.height || 0);
return Cesium.Cartesian3.distance(cartesian1, cartesian2);
}
// 计算地表距离(大地线)
function calculateSurfaceDistance(point1, point2) {
const geodesic = new Cesium.EllipsoidGeodesic(
Cesium.Cartographic.fromDegrees(point1.lon, point1.lat),
Cesium.Cartographic.fromDegrees(point2.lon, point2.lat)
);
return geodesic.surfaceDistance; // 米
}
// 使用示例
const dist = calculateDistance(
{ lon: 116.4, lat: 39.9 },
{ lon: 121.5, lat: 31.2 }
);
console.log('距离:', (dist / 1000).toFixed(2), 'km');
12.1.2 折线长度测量
// 测量折线总长度
function measurePolylineLength(positions) {
let totalDistance = 0;
for (let i = 0; i < positions.length - 1; i++) {
totalDistance += Cesium.Cartesian3.distance(positions[i], positions[i + 1]);
}
return totalDistance;
}
// 交互式测量工具
class DistanceMeasureTool {
constructor(viewer) {
this.viewer = viewer;
this.positions = [];
this.handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
this.measureEntities = [];
}
start() {
this.positions = [];
this.clearMeasureEntities();
// 左键添加点
this.handler.setInputAction((click) => {
const position = this.viewer.scene.pickPosition(click.position);
if (Cesium.defined(position)) {
this.positions.push(position);
this.addPoint(position);
if (this.positions.length > 1) {
this.updateLine();
this.showDistance();
}
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
// 右键结束
this.handler.setInputAction(() => {
this.stop();
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
}
addPoint(position) {
const entity = this.viewer.entities.add({
position: position,
point: { pixelSize: 8, color: Cesium.Color.RED }
});
this.measureEntities.push(entity);
}
updateLine() {
// 移除旧线
const oldLine = this.measureEntities.find(e => e.polyline);
if (oldLine) this.viewer.entities.remove(oldLine);
const entity = this.viewer.entities.add({
polyline: {
positions: this.positions,
width: 3,
material: Cesium.Color.YELLOW
}
});
this.measureEntities.push(entity);
}
showDistance() {
const distance = measurePolylineLength(this.positions);
const lastPos = this.positions[this.positions.length - 1];
const entity = this.viewer.entities.add({
position: lastPos,
label: {
text: this.formatDistance(distance),
font: '14px sans-serif',
fillColor: Cesium.Color.WHITE,
showBackground: true,
pixelOffset: new Cesium.Cartesian2(0, -20)
}
});
this.measureEntities.push(entity);
}
formatDistance(meters) {
if (meters >= 1000) {
return (meters / 1000).toFixed(2) + ' km';
}
return meters.toFixed(2) + ' m';
}
stop() {
this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK);
this.handler.removeInputAction(Cesium.ScreenSpaceEventType.RIGHT_CLICK);
}
clearMeasureEntities() {
this.measureEntities.forEach(e => this.viewer.entities.remove(e));
this.measureEntities = [];
}
}
// 使用
const distanceTool = new DistanceMeasureTool(viewer);
distanceTool.start();
12.2 面积测量
12.2.1 多边形面积计算
// 计算多边形面积(平面近似)
function calculatePolygonArea(positions) {
const cartographics = positions.map(p => Cesium.Cartographic.fromCartesian(p));
// 转换为平面坐标
const center = Cesium.BoundingSphere.fromPoints(positions).center;
const transform = Cesium.Transforms.eastNorthUpToFixedFrame(center);
const inverseTransform = Cesium.Matrix4.inverse(transform, new Cesium.Matrix4());
const localPositions = positions.map(p => {
return Cesium.Matrix4.multiplyByPoint(inverseTransform, p, new Cesium.Cartesian3());
});
// 鞋带公式
let area = 0;
for (let i = 0; i < localPositions.length; i++) {
const j = (i + 1) % localPositions.length;
area += localPositions[i].x * localPositions[j].y;
area -= localPositions[j].x * localPositions[i].y;
}
return Math.abs(area) / 2;
}
// 使用 Turf.js 计算更精确的面积
async function calculateAreaWithTurf(positions) {
// 需要引入 Turf.js: npm install @turf/turf
const turf = await import('@turf/turf');
const coordinates = positions.map(p => {
const c = Cesium.Cartographic.fromCartesian(p);
return [Cesium.Math.toDegrees(c.longitude), Cesium.Math.toDegrees(c.latitude)];
});
coordinates.push(coordinates[0]); // 闭合
const polygon = turf.polygon([coordinates]);
return turf.area(polygon); // 平方米
}
12.2.2 交互式面积测量
class AreaMeasureTool {
constructor(viewer) {
this.viewer = viewer;
this.positions = [];
this.handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
this.measureEntities = [];
this.tempPolygon = null;
}
start() {
this.positions = [];
this.clearMeasureEntities();
// 左键添加点
this.handler.setInputAction((click) => {
const position = this.viewer.scene.pickPosition(click.position);
if (Cesium.defined(position)) {
this.positions.push(position);
this.addPoint(position);
this.updatePolygon();
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
// 鼠标移动
this.handler.setInputAction((movement) => {
if (this.positions.length >= 2) {
const position = this.viewer.scene.pickPosition(movement.endPosition);
if (Cesium.defined(position)) {
this.updateTempPolygon(position);
}
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
// 右键结束
this.handler.setInputAction(() => {
if (this.positions.length >= 3) {
this.finish();
}
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
}
addPoint(position) {
const entity = this.viewer.entities.add({
position: position,
point: { pixelSize: 8, color: Cesium.Color.RED }
});
this.measureEntities.push(entity);
}
updatePolygon() {
if (this.positions.length < 3) return;
// 移除旧多边形
if (this.tempPolygon) {
this.viewer.entities.remove(this.tempPolygon);
}
this.tempPolygon = this.viewer.entities.add({
polygon: {
hierarchy: this.positions,
material: Cesium.Color.YELLOW.withAlpha(0.3),
outline: true,
outlineColor: Cesium.Color.YELLOW
}
});
this.measureEntities.push(this.tempPolygon);
}
updateTempPolygon(mousePosition) {
if (this.positions.length < 2) return;
const tempPositions = [...this.positions, mousePosition];
if (this.tempPolygon) {
this.tempPolygon.polygon.hierarchy = tempPositions;
} else {
this.tempPolygon = this.viewer.entities.add({
polygon: {
hierarchy: tempPositions,
material: Cesium.Color.YELLOW.withAlpha(0.3),
outline: true,
outlineColor: Cesium.Color.YELLOW
}
});
this.measureEntities.push(this.tempPolygon);
}
}
finish() {
const area = calculatePolygonArea(this.positions);
const center = Cesium.BoundingSphere.fromPoints(this.positions).center;
const entity = this.viewer.entities.add({
position: center,
label: {
text: this.formatArea(area),
font: '16px sans-serif',
fillColor: Cesium.Color.WHITE,
showBackground: true,
backgroundColor: Cesium.Color.BLACK.withAlpha(0.7)
}
});
this.measureEntities.push(entity);
this.stop();
}
formatArea(squareMeters) {
if (squareMeters >= 1000000) {
return (squareMeters / 1000000).toFixed(2) + ' km²';
}
return squareMeters.toFixed(2) + ' m²';
}
stop() {
this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK);
this.handler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE);
this.handler.removeInputAction(Cesium.ScreenSpaceEventType.RIGHT_CLICK);
}
clearMeasureEntities() {
this.measureEntities.forEach(e => this.viewer.entities.remove(e));
this.measureEntities = [];
this.tempPolygon = null;
}
}
12.3 高度测量
// 高度测量工具
class HeightMeasureTool {
constructor(viewer) {
this.viewer = viewer;
this.handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
this.measureEntities = [];
}
start() {
this.handler.setInputAction((click) => {
const position = this.viewer.scene.pickPosition(click.position);
if (Cesium.defined(position)) {
this.measureHeight(position);
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
}
async measureHeight(position) {
const cartographic = Cesium.Cartographic.fromCartesian(position);
const clickHeight = cartographic.height;
// 采样地形高度
const terrainPositions = [Cesium.Cartographic.fromRadians(
cartographic.longitude, cartographic.latitude
)];
await Cesium.sampleTerrainMostDetailed(
this.viewer.terrainProvider, terrainPositions
);
const terrainHeight = terrainPositions[0].height;
const relativeHeight = clickHeight - terrainHeight;
// 显示测量结果
const groundPosition = Cesium.Cartesian3.fromRadians(
cartographic.longitude, cartographic.latitude, terrainHeight
);
// 垂直线
const lineEntity = this.viewer.entities.add({
polyline: {
positions: [groundPosition, position],
width: 2,
material: new Cesium.PolylineDashMaterialProperty({
color: Cesium.Color.YELLOW
})
}
});
this.measureEntities.push(lineEntity);
// 标签
const labelEntity = this.viewer.entities.add({
position: position,
label: {
text: `海拔: ${clickHeight.toFixed(1)}m\n相对高度: ${relativeHeight.toFixed(1)}m`,
font: '14px sans-serif',
fillColor: Cesium.Color.WHITE,
showBackground: true,
pixelOffset: new Cesium.Cartesian2(10, 0)
}
});
this.measureEntities.push(labelEntity);
}
stop() {
this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK);
}
clear() {
this.measureEntities.forEach(e => this.viewer.entities.remove(e));
this.measureEntities = [];
}
}
12.4 坐标拾取
// 坐标拾取工具
class CoordinatePickerTool {
constructor(viewer) {
this.viewer = viewer;
this.handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
this.coordinateDisplay = null;
}
start() {
// 创建坐标显示面板
this.createDisplayPanel();
// 鼠标移动实时显示
this.handler.setInputAction((movement) => {
const position = this.getPosition(movement.endPosition);
if (position) {
this.updateDisplay(position);
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
// 点击复制坐标
this.handler.setInputAction((click) => {
const position = this.getPosition(click.position);
if (position) {
this.copyToClipboard(position);
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
}
getPosition(screenPosition) {
// 优先拾取地形
const ray = this.viewer.camera.getPickRay(screenPosition);
const position = this.viewer.scene.globe.pick(ray, this.viewer.scene);
if (Cesium.defined(position)) {
const cartographic = Cesium.Cartographic.fromCartesian(position);
return {
longitude: Cesium.Math.toDegrees(cartographic.longitude),
latitude: Cesium.Math.toDegrees(cartographic.latitude),
height: cartographic.height
};
}
return null;
}
createDisplayPanel() {
this.coordinateDisplay = document.createElement('div');
this.coordinateDisplay.style.cssText = `
position: absolute;
bottom: 10px;
left: 10px;
background: rgba(0,0,0,0.7);
color: white;
padding: 10px;
font-family: monospace;
border-radius: 4px;
z-index: 1000;
`;
this.viewer.container.appendChild(this.coordinateDisplay);
}
updateDisplay(position) {
this.coordinateDisplay.innerHTML = `
经度: ${position.longitude.toFixed(6)}°<br>
纬度: ${position.latitude.toFixed(6)}°<br>
高度: ${position.height.toFixed(2)} m<br>
<small>点击复制坐标</small>
`;
}
copyToClipboard(position) {
const text = `${position.longitude.toFixed(6)}, ${position.latitude.toFixed(6)}, ${position.height.toFixed(2)}`;
navigator.clipboard.writeText(text);
console.log('坐标已复制:', text);
}
stop() {
this.handler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE);
this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK);
if (this.coordinateDisplay) {
this.coordinateDisplay.remove();
}
}
}
12.5 本章小结
本章介绍了空间分析与测量:
- 距离测量:直线距离、地表距离、交互式测量
- 面积测量:多边形面积计算、交互式测量
- 高度测量:海拔高度、相对高度
- 坐标拾取:实时显示、复制功能
在下一章中,我们将详细介绍动画与时间系统。
12.6 思考与练习
- 实现完整的测量工具栏。
- 添加测量结果导出功能。
- 实现坡度和坡向分析。
- 开发通视分析工具。
- 实现缓冲区分析功能。

浙公网安备 33010602011771号