<!DOCTYPE html>
<html lang="en">
  <head>
    <!-- Use correct character set. -->
    <meta charset="utf-8" />
    <!-- Tell IE to use the latest, best version. -->
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <!-- Make the application on mobile take up the full browser screen and disable user scaling. -->
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"
    />
    <title>Hello World!</title>
    <script src="../Build/Cesium/Cesium.js"></script>
    <style>
      @import url(../Build/CesiumUnminified/Widgets/widgets.css);

      html,
      body,
      #cesiumContainer {
        width: 100%;
        height: 100%;
        margin: 0;
        padding: 0;
        overflow: hidden;
      }

      .control-panel {
        position: absolute;
        top: 10px;
        left: 10px;
        background: rgba(42, 42, 42, 0.95);
        padding: 15px;
        border-radius: 8px;
        color: white;
        z-index: 1000;
        min-width: 300px;
        font-family: Arial, sans-serif;
      }

      .control-panel h3 {
        margin: 0 0 10px 0;
        color: #48b;
      }

      .control-panel button {
        margin: 5px;
        padding: 8px 12px;
        background: #48b;
        border: none;
        color: white;
        border-radius: 4px;
        cursor: pointer;
        width: 100%;
      }

      .control-panel button:hover {
        background: #5a9;
      }

      .info-section {
        margin: 10px 0;
        padding: 10px;
        background: rgba(255, 255, 255, 0.1);
        border-radius: 4px;
      }

      .info-item {
        margin: 5px 0;
        font-size: 12px;
      }

      .color-legend {
        display: flex;
        height: 20px;
        margin: 5px 0;
        border-radius: 3px;
        overflow: hidden;
      }
    </style>
  </head>

  <body>
    <div id="cesiumContainer"></div>
    <div class="control-panel">
      <h3>二维贴地等高线 - IDW算法</h3>

      <button onclick="generateNewData()">🔄 生成新数据</button>
      <button onclick="toggleContours()">📊 切换等高线</button>
      <button onclick="togglePoints()">📍 切换采样点</button>
      <button onclick="toggleFill()">🎨 切换颜色填充</button>

      <div class="info-section">
        <div class="info-item">采样点: <span id="pointCount">100</span></div>
        <div class="info-item">数值范围: <span id="valueRange">-</span></div>
        <div class="info-item">等高线: <span id="contourCount">0</span></div>
        <div class="info-item">网格精度: <span id="gridSize">50</span>x50</div>
      </div>

      <div class="info-section">
        <div class="info-item">IDW参数:</div>
        <select id="powerSelect" onchange="updateParameters()">
          <option value="1">幂参数: 1 (平滑)</option>
          <option value="2" selected>幂参数: 2 (标准)</option>
          <option value="3">幂参数: 3 (锐利)</option>
        </select>
        <select id="gridSelect" onchange="updateParameters()">
          <option value="30">网格: 30x30</option>
          <option value="50" selected>网格: 50x50</option>
          <option value="70">网格: 70x70</option>
        </select>
      </div>

      <div class="info-section">
        <div class="info-item">颜色图例 (低 → 高):</div>
        <div class="color-legend" id="colorLegend"></div>
      </div>
    </div>
    <script>
      const viewer = new Cesium.Viewer("cesiumContainer");
      // 设置初始视角
      // 设置初始视角
      viewer.camera.setView({
        destination: Cesium.Cartesian3.fromDegrees(117.5, 33, 80000),
        orientation: {
          heading: 0.0,
          pitch: -0.8,
          roll: 0.0,
        },
      });

      // 全局变量
      let points = [];
      let contoursVisible = true;
      let pointsVisible = true;
      let fillVisible = true;
      let idwPower = 2;
      let gridSize = 50;

      // 数据源
      let contourDataSource = new Cesium.CustomDataSource("等高线");
      let pointDataSource = new Cesium.CustomDataSource("采样点");
      let fillDataSource = new Cesium.CustomDataSource("颜色填充");

      viewer.dataSources.add(contourDataSource);
      viewer.dataSources.add(pointDataSource);
      viewer.dataSources.add(fillDataSource);

      // 初始化
      points = generateRandomPoints();
      generateContourLines();

      function generateRandomPoints() {
        const points = [];
        const minLon = 117.0;
        const maxLon = 118.0;
        const minLat = 32.0;
        const maxLat = 34;
        const numPoints = 100;

        for (let i = 0; i < numPoints; i++) {
          const lon = minLon + Math.random() * (maxLon - minLon);
          const lat = minLat + Math.random() * (maxLat - minLat);

          // 创建一个有规律的山丘状分布
          const centerLon = 117.5;
          const centerLat = 33;
          const distance = Math.sqrt(
            Math.pow(lon - centerLon, 2) + Math.pow(lat - centerLat, 2)
          );

          // 使用高斯函数创建山丘效果
          const value =
            50 * Math.exp(-distance * distance * 10) +
            20 * Math.sin(lon * 8) +
            15 * Math.cos(lat * 8) +
            Math.random() * 10;

          points.push({
            longitude: lon,
            latitude: lat,
            height: 0,
            value: value,
          });
        }

        return points;
      }

      // 反距离加权插值
      function idwInterpolation(targetLon, targetLat, points, power = 2) {
        let numerator = 0;
        let denominator = 0;

        for (const point of points) {
          const distance = Math.sqrt(
            Math.pow(point.longitude - targetLon, 2) +
              Math.pow(point.latitude - targetLat, 2)
          );

          if (distance === 0) return point.value;

          const weight = 1 / Math.pow(distance, power);
          numerator += weight * point.value;
          denominator += weight;
        }

        return numerator / denominator;
      }

      function generateContourLines() {
        // 清除之前的数据
        clearDataSources();

        if (points.length === 0) return;

        // 计算数值范围
        const values = points.map((p) => p.value);
        const minValue = Math.min(...values);
        const maxValue = Math.max(...values);
        const valueRange = maxValue - minValue;

        // 生成网格数据
        const gridData = generateGridData(minValue, maxValue, points);

        // 生成颜色填充
        if (fillVisible) {
          generateColorFill(gridData, minValue, maxValue);
        }

        // 生成等高线
        generateContours(gridData, minValue, maxValue);

        // 显示采样点
        if (pointsVisible) {
          showSamplingPoints(minValue, maxValue);
        }

        updateColorLegend(minValue, maxValue);
      }

      function generateGridData(minValue, maxValue, points) {
        const gridLons = [];
        const gridLats = [];
        const gridValues = [];

        const longitudes = points.map((p) => p.longitude);
        const minLongitudes = Math.min(...longitudes);
        const maxLongitudes = Math.max(...longitudes);

        const latitudes = points.map((p) => p.latitude);
        const minLatitudes = Math.min(...latitudes);
        const maxLatitudes = Math.max(...latitudes);

        // 生成网格坐标
        for (let i = 0; i < gridSize; i++) {
          gridLons[i] =
            minLongitudes +
            (i / (gridSize - 1)) * (maxLongitudes - minLongitudes);
          gridLats[i] =
            minLatitudes + (i / (gridSize - 1)) * (maxLatitudes - minLatitudes);
          gridValues[i] = new Array(gridSize);
        }

        // 计算每个网格点的IDW值
        for (let i = 0; i < gridSize; i++) {
          for (let j = 0; j < gridSize; j++) {
            gridValues[i][j] = idwInterpolation(
              gridLons[i],
              gridLats[j],
              points,
              idwPower
            );
          }
        }

        return { gridLons, gridLats, gridValues, minValue, maxValue };
      }

      function generateColorFill(gridData, minValue, maxValue) {
        const { gridLons, gridLats, gridValues } = gridData;
        const valueRange = maxValue - minValue;

        // 为每个网格单元创建颜色填充
        for (let i = 0; i < gridSize - 1; i++) {
          for (let j = 0; j < gridSize - 1; j++) {
            const value = gridValues[i][j];
            const normalizedValue = (value - minValue) / valueRange;

            // 计算颜色
            const hue = (1 - normalizedValue) * 240;
            const color = Cesium.Color.fromHsl(hue / 360, 0.7, 0.5, 0.6);

            // 创建网格单元的多边形
            const positions = [
              Cesium.Cartesian3.fromDegrees(gridLons[i], gridLats[j]),
              Cesium.Cartesian3.fromDegrees(gridLons[i + 1], gridLats[j]),
              Cesium.Cartesian3.fromDegrees(gridLons[i + 1], gridLats[j + 1]),
              Cesium.Cartesian3.fromDegrees(gridLons[i], gridLats[j + 1]),
            ];

            fillDataSource.entities.add({
              polygon: {
                hierarchy: positions,
                material: color,
                height: 0,
                extrudedHeight: 0,
                outline: false,
                heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
              },
            });
          }
        }
      }

      function generateContours(gridData, minValue, maxValue) {
        const { gridLons, gridLats, gridValues } = gridData;
        const valueRange = maxValue - minValue;
        const contourInterval = valueRange / 15;
        const numContours = Math.ceil(valueRange / contourInterval);

        document.getElementById("contourCount").textContent = numContours;

        // 为每个等高线级别生成线条
        for (let level = 0; level <= numContours; level++) {
          const contourValue = minValue + level * contourInterval;
          const contourLines = [];

          // 遍历网格单元,寻找等高线
          for (let i = 0; i < gridSize - 1; i++) {
            for (let j = 0; j < gridSize - 1; j++) {
              const cellValues = [
                gridValues[i][j],
                gridValues[i + 1][j],
                gridValues[i + 1][j + 1],
                gridValues[i][j + 1],
              ];

              const cellPoints = [
                { lon: gridLons[i], lat: gridLats[j] },
                { lon: gridLons[i + 1], lat: gridLats[j] },
                { lon: gridLons[i + 1], lat: gridLats[j + 1] },
                { lon: gridLons[i], lat: gridLats[j + 1] },
              ];

              // 寻找等高线与网格边的交点
              const intersections = [];
              for (let k = 0; k < 4; k++) {
                const v1 = cellValues[k];
                const v2 = cellValues[(k + 1) % 4];

                if (
                  (v1 < contourValue && v2 >= contourValue) ||
                  (v1 >= contourValue && v2 < contourValue)
                ) {
                  const t = (contourValue - v1) / (v2 - v1);
                  const lon =
                    cellPoints[k].lon +
                    t * (cellPoints[(k + 1) % 4].lon - cellPoints[k].lon);
                  const lat =
                    cellPoints[k].lat +
                    t * (cellPoints[(k + 1) % 4].lat - cellPoints[k].lat);

                  intersections.push(Cesium.Cartesian3.fromDegrees(lon, lat));
                }
              }

              // 如果有两个交点,创建等高线段
              if (intersections.length === 2) {
                contourLines.push({
                  positions: intersections,
                  value: contourValue,
                });
              }
            }
          }

          // 绘制等高线
          if (contourLines.length > 0) {
            const normalizedValue = (contourValue - minValue) / valueRange;
            const hue = (1 - normalizedValue) * 240;
            const color = Cesium.Color.fromHsl(hue / 360, 0.9, 0.3);

            // 为每个等高线段创建实体
            contourLines.forEach((line) => {
              contourDataSource.entities.add({
                polyline: {
                  positions: line.positions,
                  width: 2,
                  material: color,
                  clampToGround: true,
                },
              });
            });

            // 添加等高线标签
            if (contourLines.length > 0 && level % 2 === 0) {
              const midPoint =
                contourLines[Math.floor(contourLines.length / 2)].positions[0];
              contourDataSource.entities.add({
                position: midPoint,
                label: {
                  text: contourValue.toFixed(0),
                  font: "12pt Arial",
                  fillColor: Cesium.Color.BLACK,
                  outlineColor: Cesium.Color.WHITE,
                  outlineWidth: 2,
                  style: Cesium.LabelStyle.FILL_AND_OUTLINE,
                  pixelOffset: new Cesium.Cartesian2(0, 0),
                  heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
                },
              });
            }
          }
        }
      }

      function showSamplingPoints(minValue, maxValue) {
        const valueRange = maxValue - minValue;

        points.forEach((point, index) => {
          const normalizedValue = (point.value - minValue) / valueRange;
          const hue = (1 - normalizedValue) * 240;
          const color = Cesium.Color.fromHsl(hue / 360, 0.8, 0.5);

          pointDataSource.entities.add({
            position: Cesium.Cartesian3.fromDegrees(
              point.longitude,
              point.latitude
            ),
            point: {
              pixelSize: 8,
              color: color,
              outlineColor: Cesium.Color.BLACK,
              outlineWidth: 1,
              heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
              disableDepthTestDistance: Number.POSITIVE_INFINITY,
            },
            label: {
              text: point.value.toFixed(0),
              font: "10pt Arial",
              pixelOffset: new Cesium.Cartesian2(0, -15),
              fillColor: Cesium.Color.BLACK,
              outlineColor: Cesium.Color.WHITE,
              outlineWidth: 2,
              style: Cesium.LabelStyle.FILL_AND_OUTLINE,
              heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
            },
          });
        });
      }

      function clearDataSources() {
        contourDataSource.entities.removeAll();
        pointDataSource.entities.removeAll();
        fillDataSource.entities.removeAll();
      }

      function updateColorLegend(minValue, maxValue) {
        const legend = document.getElementById("colorLegend");
        legend.innerHTML = "";

        for (let i = 0; i < 10; i++) {
          const normalizedValue = i / 9;
          const hue = (1 - normalizedValue) * 240;
          const color = Cesium.Color.fromHsl(hue / 360, 0.7, 0.5);
          const colorHex = Cesium.Color.toCssColorString(color);

          const colorBar = document.createElement("div");
          colorBar.style.flex = "1";
          colorBar.style.background = colorHex;
          colorBar.title = `${(
            minValue +
            normalizedValue * (maxValue - minValue)
          ).toFixed(1)}`;
          legend.appendChild(colorBar);
        }
      }

      function toggleContours() {
        contoursVisible = !contoursVisible;
        contourDataSource.show = contoursVisible;
      }

      function togglePoints() {
        pointsVisible = !pointsVisible;
        pointDataSource.show = pointsVisible;
      }

      function toggleFill() {
        fillVisible = !fillVisible;
        fillDataSource.show = fillVisible;
      }

      function updateParameters() {
        idwPower = parseInt(document.getElementById("powerSelect").value);
        gridSize = parseInt(document.getElementById("gridSelect").value);
        generateContourLines();
      }
    </script>
  </body>
</html>

效果截图:

0AE06658-CCBD-497f-B0C9-31AD515D422B