采用Canvas绘制贴地矩形的材质
核心
通过等分矩形区域获取格网点坡度,将数据映射到Canvas上。
注意点
调试的时候一直出现条纹状材质,原因是起初通过turf.squareGrids确定格网点,出现canvas与原始数据对应不上的问题。由于canvas坐标系统以左上角为原点,自左向右,自上向下,因此需要确保原始数据的顺序也以此为准。
源码
1.绘制区域
假设动态绘制出矩形区域,通过turf.square生成正方形区域(方便调试)
2.采集数据
// 测试区域 const extent = turf.square([119.95, 29.95, 120, 30])
// 通过按需引入cesium的方法,如import {Rectangle} from "cesium" 可自行添加
addSlopeCanvas(extent: turf.BBox) {
// 获取包围盒坐标,res [119.95, 29.95, 120, 30]
const polygonPos = turf.getCoord(extent);
// 存储在全局
this.polygonPos = polygonPos;
const rectangle = Rectangle.fromDegrees(...polygonPos);
const width = 50; // 横向点数
const height = 50; // 纵向点数
const positions = [];
// 格网度数,为了等分矩形
const dx = (polygonPos[2] - polygonPos[0]) / width;
const dy = (polygonPos[1] - polygonPos[1]) / height;
// 格网距离,为了计算坡度
const ddx =
Cartesian3.distance(
Cartesian3.fromDegrees(polygonPos[0], polygonPos[1]),
Cartesian3.fromDegrees(polygonPos[2], polygonPos[1])
) / width;
const ddy =
Cartesian3.distance(
Cartesian3.fromDegrees(polygonPos[0], polygonPos[1]),
Cartesian3.fromDegrees(polygonPos[0], polygonPos[3])
) / height;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
// 插值 这里开始规划数据顺序,以矩形区域左上角为第一点,自左向右,自上到下
const longitude = CesiumMath.toDegrees(
CesiumMath.lerp(rectangle.west, rectangle.east, x / (width - 1))
);
const latitude = CesiumMath.toDegrees(
CesiumMath.lerp(rectangle.north, rectangle.south, y / (height - 1))
);
// 八连通点
positions.push(Cartographic.fromDegrees(longitude - dx / 2, latitude)); // w
positions.push(
Cartographic.fromDegrees(longitude - dx / 2, latitude - dy / 2)
); // ws
positions.push(Cartographic.fromDegrees(longitude, latitude + dy / 2)); // n
positions.push(
Cartographic.fromDegrees(longitude - dx / 2, latitude + dy / 2)
); // wn
positions.push(Cartographic.fromDegrees(longitude + dx / 2, latitude)); // e
positions.push(
Cartographic.fromDegrees(longitude + dx / 2, latitude + dy / 2)
); // en
positions.push(Cartographic.fromDegrees(longitude, latitude - dy / 2)); // s
positions.push(
Cartographic.fromDegrees(longitude + dx / 2, latitude - dy / 2)
); // es
// 顶点
positions.push(Cartographic.fromDegrees(longitude, latitude)); // mid
}
}
// console.log(positions);
const slopes: number[] = [];
sampleTerrainMostDetailed(this.viewer.terrainProvider, positions).then(
(updatedPositions) => {
for (let i = 0; i < updatedPositions.length; i += 9) {
// 备用中心点
// let center = updatedPositions[i + 8];
// Horn算法,常用的坡度算法
// fx = (ws - wn + 2(s - n) + es - en)/(8*GridSize)
// fy = (en - wn + 2(e - w) + es - ws)/(8*GridSize)
// slope = arctan(sqrt(fx^2 + fy^2)) * 180 / PI
const westHeight = updatedPositions[i + 0].height;
const westSouthHeight = updatedPositions[i + 1].height;
const northHeight = updatedPositions[i + 2].height;
const westNorthHeight = updatedPositions[i + 3].height;
const eastHeight = updatedPositions[i + 4].height;
const eastNorthHeight = updatedPositions[i + 5].height;
const southHeight = updatedPositions[i + 6].height;
const eastSouthHeight = updatedPositions[i + 7].height;
const fx =
(westSouthHeight +
2 * southHeight +
eastSouthHeight -
(westNorthHeight + 2 * northHeight + eastNorthHeight)) /
(8 * ddx);
const fy =
(eastNorthHeight +
2 * eastHeight +
eastSouthHeight -
(westNorthHeight + 2 * westHeight + westSouthHeight)) /
(8 * ddy);
const slope = Math.atan(Math.sqrt(fx ** 2 + fy ** 2));
slopes.push(slope);
}
this.slopes = slopes;
this.createMaterial();
}
);
}
3. 创建几何体材质
createMaterial() {
// 见第四点
const canvas = this.toCanvas();
// this.polygonPos 全局存储的矩形区域四至点
const positions = [
Cartesian3.fromDegrees(this.polygonPos[0], this.polygonPos[1]),
Cartesian3.fromDegrees(this.polygonPos[0], this.polygonPos[3]),
Cartesian3.fromDegrees(this.polygonPos[2], this.polygonPos[3]),
Cartesian3.fromDegrees(this.polygonPos[2], this.polygonPos[1]),
];
// 将canvas生成的图像作为贴地几何体材质
const polygon = this.viewer.entities.add({
polygon: {
hierarchy: new PolygonHierarchy(positions),
material: new ImageMaterialProperty({
image: canvas,
}),
classificationType: ClassificationType.BOTH,
},
});
}
4.生成图像
// 重点是数据与图像像素一一对应
toCanvas() {
const w = 50;
const h = 50;
const canvas = document.createElement("canvas");
canvas.height = h;
canvas.width = w;
const ctx: any = canvas.getContext("2d");
const bitmap = new Uint8ClampedArray(w * h * 4); // 各像素点rgba
// console.log(this.slopes);
for (let y = 0; y < h; y++) {
for (let x = 0; x < w; x++) {
// 左上角为坐标原点
const slope = this.slopes[y * w + x];
// hsl颜色有过渡性slope / (Math.PI / 2)转为0-1,也可以直接写slope / (Math.PI / 2)
const hue = (1 - slope / (Math.PI / 2)) / 2;
const color = Color.fromHsl(hue, 1, 0.5).withAlpha(
0.5
);
const bitmapIndex = (y * w + x) * 4;
bitmap[bitmapIndex + 0] = color.red * 255;
bitmap[bitmapIndex + 1] = color.green * 255;
bitmap[bitmapIndex + 2] = color.blue * 255;
bitmap[bitmapIndex + 3] = color.alpha * 255;
}
}
const imageData = new ImageData(bitmap, w, h);
ctx.putImageData(imageData, 0, 0);
return canvas;
}