cesium通过geoJson渲染立体楼栋

CesiumJs yekong

最近开发一个数据可视化大屏,需要渲染楼栋,但是手里并没有楼栋的数据,今天我们来记录如何获取geoJson以及渲染geoJson数据为楼栋。

cesium通过geoJson渲染立体楼栋

获取geoJson数据

这里我们使用到了阿里云的边界生成器 .

我们找到我们想要的小区放大地图到最大,这样我们就可以看到楼栋的轮廓了,

楼栋的轮廓

我们通过多边形工具,将大楼轮廓勾勒出来,这样就可以获取到这个大楼的轮廓了,接下来我们导出geoJson数据,然后增加字段,我们手动查询这个小区各个楼栋的高度。

获取geoJson

然后我们将获取到的楼栋高度回填到geoJson中的height,这样我们的楼栋数据就大概成型了。

{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "id": 14969,
      "properties": {
        "_draw_type": "fill",
        "name": "1座",
        "height": 46
      },
      "geometry": {
        "type": "Polygon",
        "coordinates": [
          [
            [
              113.14957777,
              22.98884755
            ],
            [
              113.14990915,
              22.9888465
            ],
            [
              113.14990857,
              22.98868735
            ],
            [
              113.14957397,
              22.98868732
            ],
            [
              113.14957299,
              22.98868911
            ],
            [
              113.14957777,
              22.98884755
            ]
          ]
        ]
      },
      "bbox": [
        113.14957299,
        22.98868732,
        113.14990915,
        22.98884755
      ]
    }
  ]
}

数据渲染

数据渲染,接下来,我们进行数据渲染

<script setup>
import * as Cesium from 'cesium'
import 'cesium/Build/Cesium/Widgets/widgets.css'
import {onMounted, ref, onUnmounted} from "vue"
import loudong from './data/loudong.json'
import {
  gcj02towgs84
} from './coordTransform.js'
import labelCom from './label/index.vue'
import {CESIUM_TOKEN} from '@/config/config.js'

const FLOOR_HEIGHT = 3;
const labelPositions = ref([])
const selectedEntity = ref(null)
// 添加当前高亮索引
const currentIndex = ref(0)
// 添加定时器引用
let highlightTimer = null

Cesium.Ion.defaultAccessToken = CESIUM_TOKEN

onMounted(async () => {
  const viewer = new Cesium.Viewer('cesiumBody', {
    infoBox: true,
    geocoder: false,
    homeButton: false,
    sceneModePicker: false,
    baseLayerPicker: false,
    navigationHelpButton: false,
    animation: false,
    timeline: false,
    fullscreenButton: false,
    terrainProvider: await Cesium.createWorldTerrainAsync(),
    scene3DOnly: true,
    shouldAnimate: true
  })

  viewer.cesiumWidget.creditContainer.style.display = 'none'
  viewer.scene.globe.enableLighting = true
  viewer.scene.globe.depthTestAgainstTerrain = true

  try {
    const features = loudong.features.map(feature => {
      const coordinates = feature.geometry.coordinates[0].map(coord => {
        const [lng, lat] = gcj02towgs84(coord[0], coord[1]);
        return [lng, lat];
      });
      return {
        ...feature,
        geometry: {
          ...feature.geometry,
          coordinates: [coordinates]
        }
      };
    });

    const transformedGeoJSON = {
      type: "FeatureCollection",
      features: features
    };

    viewer.camera.flyTo({
      destination: Cesium.Cartesian3.fromDegrees(113.14572076, 22.99590083, 411.50),
      orientation: {
        heading: Cesium.Math.toRadians(184.93),
        pitch: Cesium.Math.toRadians(-26.38),
        roll: 0.00
      },
      duration: 3
    });

    const osmBuildings = await Cesium.createOsmBuildingsAsync()
    viewer.scene.primitives.add(osmBuildings)

    const geojsonOptions = {
      clampToGround: true,
      stroke: Cesium.Color.WHITE,
      fill: Cesium.Color.fromCssColorString('#22d4f5').withAlpha(0.6),
      strokeWidth: 2,
    };

    const dataSource = await Cesium.GeoJsonDataSource.load(transformedGeoJSON, geojsonOptions);
    viewer.dataSources.add(dataSource);

    const entities = dataSource.entities.values;
    entities.forEach(entity => {
      if (entity.polygon) {
        const floorCount = entity.properties.height?._value || 1;
        const buildingHeight = floorCount * FLOOR_HEIGHT;

        entity.polygon.extrudedHeight = buildingHeight;
        entity.polygon.material = new Cesium.ColorMaterialProperty(
            Cesium.Color.fromCssColorString('#22d4f5').withAlpha(0.6)
        );
        entity.polygon.outline = true;
        entity.polygon.outlineColor = Cesium.Color.WHITE;
        entity.polygon.classificationType = Cesium.ClassificationType.BOTH;
        entity.polygon.heightReference = Cesium.HeightReference.RELATIVE_TO_GROUND;

        const positions = entity.polygon.hierarchy.getValue().positions;
        const centerCartesian = Cesium.BoundingSphere.fromPoints(positions).center;
        const centerCartographic = Cesium.Cartographic.fromCartesian(centerCartesian);

        const longitude = Cesium.Math.toDegrees(centerCartographic.longitude);
        const latitude = Cesium.Math.toDegrees(centerCartographic.latitude);

        labelPositions.value.push({
          position: Cesium.Cartesian3.fromDegrees(longitude, latitude, buildingHeight + 5),
          name: entity.properties.name._value,
          height: buildingHeight + 5,
          screenX: 0,
          screenY: 0
        })
      }
    });

    // 添加自动轮播高亮效果
    const highlightNextBuilding = () => {
      if (selectedEntity.value) {
        selectedEntity.value.polygon.material = new Cesium.ColorMaterialProperty(
            Cesium.Color.fromCssColorString('#22d4f5').withAlpha(0.6)
        );
      }

      currentIndex.value = (currentIndex.value + 1) % entities.length;
      const entity = entities[currentIndex.value];

      entity.polygon.material = new Cesium.ColorMaterialProperty(
          Cesium.Color.YELLOW.withAlpha(1)
      );
      selectedEntity.value = entity;
    }

    // 启动定时器,每3秒切换一次高亮
    highlightTimer = setInterval(highlightNextBuilding, 3000);

    viewer.scene.postRender.addEventListener(() => {
      labelPositions.value.forEach(item => {
        const windowPosition = Cesium.SceneTransforms.wgs84ToWindowCoordinates(
            viewer.scene,
            item.position
        )
        if (windowPosition) {
          item.screenX = windowPosition.x
          item.screenY = windowPosition.y
        }
      })
    })

    viewer.screenSpaceEventHandler.setInputAction((movement) => {
      const camera = viewer.camera;
      const position = camera.position;
      const cartographic = Cesium.Cartographic.fromCartesian(position);

      console.log('相机位置:', {
        heading: Cesium.Math.toDegrees(camera.heading).toFixed(2),
        pitch: Cesium.Math.toDegrees(camera.pitch).toFixed(2),
        roll: Cesium.Math.toDegrees(camera.roll).toFixed(2),
        longitude: Cesium.Math.toDegrees(cartographic.longitude).toFixed(8),
        latitude: Cesium.Math.toDegrees(cartographic.latitude).toFixed(8),
        height: cartographic.height.toFixed(2)
      });
    }, Cesium.ScreenSpaceEventType.RIGHT_CLICK);

    // 添加点击事件处理
    viewer.screenSpaceEventHandler.setInputAction((movement) => {
      const pickedFeature = viewer.scene.pick(movement.position);

      // 清除定时器,停止自动轮播
      if(highlightTimer) {
        clearInterval(highlightTimer);
        highlightTimer = null;
      }

      if (selectedEntity.value) {
        // 恢复之前选中实体的样式
        selectedEntity.value.polygon.material = new Cesium.ColorMaterialProperty(
            Cesium.Color.fromCssColorString('#22d4f5').withAlpha(0.6)
        );
      }

      if (Cesium.defined(pickedFeature) && pickedFeature.id) {
        // 设置新选中实体的高亮样式
        pickedFeature.id.polygon.material = new Cesium.ColorMaterialProperty(
            Cesium.Color.YELLOW.withAlpha(1)
        );
        selectedEntity.value = pickedFeature.id;

        // 更新当前索引
        const entities = dataSource.entities.values;
        currentIndex.value = entities.indexOf(pickedFeature.id);

        console.log('选中建筑:', pickedFeature.id.properties.name?._value);
      } else {
        selectedEntity.value = null;
      }
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);


  } catch (error) {
    console.error('加载失败:', error)
  }
})

// 组件卸载时清除定时器
onUnmounted(() => {
  if (highlightTimer) {
    clearInterval(highlightTimer)
  }
})
</script>

最终效果如下

cesium通过geoJson渲染立体楼栋

cesium版本

"cesium": "1.114.0",

项目应用

vue3 数据可视化大屏 外籍人员管理

喜欢