一、生成的las文件
python代码:
import laspy
import numpy as np
from pymap3d import geodetic2ecef
# ===================== 你的区域坐标 =====================
coordinates = np.array([
[118.4573848, 44.8212813],
[118.4783848, 44.8212813],
[118.4783848, 44.8401613],
[118.4573848, 44.8401613]
])
min_lon, min_lat = coordinates.min(axis=0)
max_lon, max_lat = coordinates.max(axis=0)
# 点密度
step = 0.00008
# 生成网格点
lons = np.arange(min_lon, max_lon, step)
lats = np.arange(min_lat, max_lat, step)
lon_grid, lat_grid = np.meshgrid(lons, lats)
lon = lon_grid.flatten()
lat = lat_grid.flatten()
# ===================== ✅ 固定高度 = 0(完全贴地)=====================
height = np.zeros_like(lon) # 所有点高度 = 0
# 坐标转 ECEF
x, y, z = geodetic2ecef(lat, lon, height)
# 彩色
r = ((lon - lon.min())/(lon.max()-lon.min()) * 255).astype(np.uint16)
g = ((lat - lat.min())/(lat.max()-lat.min()) * 255).astype(np.uint16)
b = np.full_like(r, 255, dtype=np.uint16)
# ===================== 生成 LAS =====================
las = laspy.create(file_version="1.2", point_format=2)
las.x = x
las.y = y
las.z = z
las.red = r
las.green = g
las.blue = b
las.header.x_scale = 0.001
las.header.y_scale = 0.001
las.header.z_scale = 0.001
las.write("ground_zero_height.las")
print(f"✅ 贴地点云生成完成(高度=0)!点数:{len(lon)}")
二、转换成3D tiles
三、Cesium中加载
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Cesium GeoJSON 线要素加载示例</title>
<!-- 引入 Cesium 库(这里使用官方 CDN,你也可以替换为本地路径) -->
<script src="https://unpkg.com/cesium@1.138.0/Build/Cesium/Cesium.js"></script>
<link
href="https://unpkg.com/cesium@1.138.0/Build/Cesium/Widgets/widgets.css"
rel="stylesheet"
/>
<style>
html,
body,
#cesiumContainer {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="cesiumContainer"></div>
<script type="module">
// 1. 初始化 Cesium 地图(替换 MFmap 为原生 Cesium 初始化)
Cesium.Ion.defaultAccessToken =
"defaultAccessToken"; // 替换为你的 Token,可从 Cesium 官网获取
const viewer = new Cesium.Viewer("cesiumContainer", {});
// // 添加到地图
// viewer.imageryLayers.addImageryProvider(gaodeImagery);
// 定义异步加载函数
async function loadLocalTileset() {
try {
// 拼接本地服务器URL
// 注意:端口号需与你启动的服务器端口一致
const url = "./3DTILES/point-cloud/tileset.json";
// 调用最新的 fromUrl 方法
const tileset = await Cesium.Cesium3DTileset.fromUrl(url, {
maximumScreenSpaceError: 0.5, // 强制最高精度
skipLevelOfDetail: true, // 强制加载所有点
dynamicScreenSpaceError: false, // 关闭自动隐藏
cullRequestsWhileMoving: false, // 移动时不卸载
cullWithChildrenBounds: false,
});
tileset.style = new Cesium.Cesium3DTileStyle({
pointSize: 3.0,
color: {
conditions: [
// 正确读取 LAS 转 3D Tiles 后的颜色(RGB 通道)
[true, "rgb(${R}, ${G}, ${B})"],
],
},
});
// 添加到场景
viewer.scene.primitives.add(tileset);
// 调试:等待tileset加载完成后查看位置
console.log("Tileset边界球中心:", tileset.boundingSphere.center);
console.log("边界球半径:", tileset.boundingSphere.radius);
// 尝试创建一个红色标记点来辅助定位
const centerPoint = viewer.entities.add({
position: tileset.boundingSphere.center,
point: {
color: Cesium.Color.RED,
pixelSize: 10,
},
});
viewer.flyTo(tileset, {
duration: 1.5,
offset: new Cesium.HeadingPitchRange(
Cesium.Math.toRadians(0),
Cesium.Math.toRadians(-90), // 垂直向下
30, // 30米高度
),
});
// 尝试将相机移动到tileset位置
const workScope = [
[118.4573848, 44.8212813],
[118.4783848, 44.8212813],
[118.4783848, 44.8401613],
[118.4573848, 44.8401613],
];
const { center, cameraHeight } = await getCenterAndCameraHeight(
viewer,
Cesium,
workScope,
);
// flyToWorkScope(center[0], center[1], cameraHeight);
// limitDisplayLevels(cameraHeight);
} catch (error) {
console.error("加载失败:", error);
}
}
loadLocalTileset();
function getCenterAndCameraHeight(viewer, Cesium, points) {
// 参数验证
if (!viewer) {
throw new Error("[getCenterAndCameraHeight] viewer参数不能为空");
}
if (!Cesium) {
throw new Error("[getCenterAndCameraHeight] Cesium参数不能为空");
}
if (!Array.isArray(points) || points.length === 0) {
throw new Error("[getCenterAndCameraHeight] points必须是非空数组");
}
// 验证每个坐标点
for (let i = 0; i < points.length; i++) {
const point = points[i];
if (!Array.isArray(point) || point.length !== 2) {
throw new Error(
`[getCenterAndCameraHeight] 第${i}个坐标点格式错误,应为[经度, 纬度]`,
);
}
const [lng, lat] = point;
if (
typeof lng !== "number" ||
isNaN(lng) ||
lng < -180 ||
lng > 180
) {
throw new Error(
`[getCenterAndCameraHeight] 第${i}个坐标点经度无效: ${lng}`,
);
}
if (typeof lat !== "number" || isNaN(lat) || lat < -90 || lat > 90) {
throw new Error(
`[getCenterAndCameraHeight] 第${i}个坐标点纬度无效: ${lat}`,
);
}
}
try {
// 提取经纬度范围
const lngs = points.map((p) => p[0]);
const lats = points.map((p) => p[1]);
const minLng = Math.min(...lngs);
const minLat = Math.min(...lats);
const maxLng = Math.max(...lngs);
const maxLat = Math.max(...lats);
// 构建矩形范围
const rectangle = Cesium.Rectangle.fromDegrees(
minLng,
minLat,
maxLng,
maxLat,
);
// 验证矩形有效性
if (rectangle.width <= 0 || rectangle.height <= 0) {
throw new Error(
`[getCenterAndCameraHeight] 矩形范围无效: 宽度=${rectangle.width}, 高度=${rectangle.height}`,
);
}
// 计算几何中心点
const centerCartographic = Cesium.Rectangle.center(rectangle);
const center = [
Cesium.Math.toDegrees(centerCartographic.longitude),
Cesium.Math.toDegrees(centerCartographic.latitude),
];
// 计算最佳相机高度
// 检查方法是否存在(兼容不同Cesium版本)
if (!viewer.camera.getRectangleCameraCoordinates) {
// 降级方案:使用默认高度计算
console.warn(
"[getCenterAndCameraHeight] getRectangleCameraCoordinates方法不存在,使用降级方案",
);
const diagonalDistance = calculateDiagonalDistance(
minLng,
minLat,
maxLng,
maxLat,
);
const cameraHeight =
diagonalDistance / Math.tan(Cesium.Math.toRadians(30)); // 默认30度视角
return { center, cameraHeight };
}
const cameraPosition =
viewer.camera.getRectangleCameraCoordinates(rectangle);
if (!cameraPosition) {
throw new Error("[getCenterAndCameraHeight] 无法计算相机位置坐标");
}
const cartographic =
Cesium.Cartographic.fromCartesian(cameraPosition);
const cameraHeight = cartographic.height;
// 验证计算结果
if (
typeof cameraHeight !== "number" ||
isNaN(cameraHeight) ||
cameraHeight <= 0
) {
throw new Error(
`[getCenterAndCameraHeight] 相机高度计算结果无效: ${cameraHeight}`,
);
}
return { center, cameraHeight };
} catch (error) {
const errorMsg =
error instanceof Error ? error.message : String(error);
throw new Error(`[getCenterAndCameraHeight] 计算失败: ${errorMsg}`);
}
}
function flyToWorkScope(lon, lat, height) {
if (!viewer || !Cesium) {
throw new Error(`[flyToWorkScope] Cesium/Viewer 未初始化`);
}
if (typeof lon !== "number" || isNaN(lon) || lon < -180 || lon > 180) {
throw new Error(
`[flyToWorkScope] 经度无效: ${lon},应为 -180 到 180 之间的数字`,
);
}
if (typeof lat !== "number" || isNaN(lat) || lat < -90 || lat > 90) {
throw new Error(
`[flyToWorkScope] 纬度无效: ${lat},应为 -90 到 90 之间的数字`,
);
}
if (typeof height !== "number" || isNaN(height)) {
throw new Error(`[flyToWorkScope] height无效: ${height},应为数字`);
}
viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(lon, lat, height),
orientation: {
heading: 0,
pitch: Cesium.Math.toRadians(-90), // 正俯视
roll: 0,
},
duration: 1,
});
}
function limitDisplayLevels(cameraHeight) {
if (!viewer || !Cesium) {
throw new Error(`[limitDisplayLevels] Cesium/Viewer 未初始化`);
}
if (typeof cameraHeight !== "number" || isNaN(cameraHeight)) {
throw new Error(
`[limitDisplayLevels] cameraHeight无效: ${cameraHeight},应为数字`,
);
}
const TARGET_MAX_LEVEL = 18;
const levelToDistance = (level) => {
const earthRadius = 6378137;
return (earthRadius * 2 * Math.PI) / Math.pow(2, level);
};
const maxDistance = levelToDistance(TARGET_MAX_LEVEL);
const controller = viewer.scene.screenSpaceCameraController;
controller.inertiaScrollEnabled = false;
controller.inertiaZoomEnabled = false;
controller.minimumZoomDistance = maxDistance + 1;
controller.maximumZoomDistance = cameraHeight + 1000000;
}
</script>
</body>
</html>
1195

被折叠的 条评论
为什么被折叠?



