第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 本章小结

本章介绍了空间分析与测量:

  1. 距离测量:直线距离、地表距离、交互式测量
  2. 面积测量:多边形面积计算、交互式测量
  3. 高度测量:海拔高度、相对高度
  4. 坐标拾取:实时显示、复制功能

在下一章中,我们将详细介绍动画与时间系统。

12.6 思考与练习

  1. 实现完整的测量工具栏。
  2. 添加测量结果导出功能。
  3. 实现坡度和坡向分析。
  4. 开发通视分析工具。
  5. 实现缓冲区分析功能。
posted @ 2026-01-08 11:13  我才是银古  阅读(14)  评论(0)    收藏  举报