Hi,
Sorry for the delay, I didn’t have the time to write this until now.
Here’s the gist of my solution:
import { Request, TerrainData, Rectangle } from 'cesium';
import * as turf from '@turf/turf';
import { Feature, Polygon } from '@turf/turf';
const MAX_SHORT = 32767;
interface ModelEdit {
polygon: Feature<Polygon>;
polygonTriangles: Feature<Polygon>[]; // TIN algorithm result on polygon
}
export class TerrainProviderEdit extends Cesium.CesiumTerrainProvider {
private modelEdits: ModelEdit[] = [];
constructor({ url, modelEdits }: { url: string; modelEdits: ModelEdit[] }) {
super({ url });
this.modelEdits = modelEdits;
}
requestTileGeometry(x: number, y: number, level: number, request: Request): Promise<TerrainData> | undefined {
const promise = super.requestTileGeometry(x, y, level, request);
if (!promise || this.modelEdits.length === 0) {
return promise;
}
const tileRectangle: Rectangle = this.tilingScheme.tileXYToRectangle(x, y, level);
const tilePolygon = GeoUtils.rectangleToPolygon(tileRectangle); // Create turf polygon from tile rectangle
const relevantEdits = this.modelEdits.filter(
edit => turf.booleanOverlap(edit.polygon, tilePolygon) || turf.booleanContains(edit.polygon, tilePolygon)
);
if (relevantEdits.length === 0) {
return promise;
}
return promise.then((data: TerrainData) => this.modifyTerrainTile(data, tileRectangle, relevantEdits));
}
private modifyTerrainTile(terrainData: TerrainData, tileRectangle: Rectangle, modelEdits: ModelEdit[]) {
const data = terrainData as any;
const minimumHeight = data._minimumHeight;
const maximumHeight = data._maximumHeight;
const quantizedVertices: Uint16Array = data._quantizedVertices;
const vertexCount = quantizedVertices.length / 3;
const positions: number[][] = [];
for (let i = 0; i < vertexCount; i++) {
const rawU = quantizedVertices[i];
const rawV = quantizedVertices[i + vertexCount];
const rawH = quantizedVertices[i + vertexCount * 2];
const u = rawU / MAX_SHORT;
const v = rawV / MAX_SHORT;
const longitude = Cesium.Math.toDegrees(Cesium.Math.lerp(tileRectangle.west, tileRectangle.east, u));
const latitude = Cesium.Math.toDegrees(Cesium.Math.lerp(tileRectangle.south, tileRectangle.north, v));
let height = Cesium.Math.lerp(minimumHeight, maximumHeight, rawH / MAX_SHORT);
const currentPoint = turf.point([longitude, latitude]);
const relevantEdit = modelEdits.find(edit => turf.booleanPointInPolygon(currentPoint, edit.polygon));
if (relevantEdit) {
const relevantTriangle = relevantEdit.polygonTriangles.find(triangle =>
turf.booleanPointInPolygon(currentPoint, triangle)
);
if (relevantTriangle) {
height = turf.planepoint(currentPoint, relevantTriangle);
}
}
positions.push([longitude, latitude, height]);
}
// TODO: split mesh triangles that are crossing the user polygon's perimiter and recreate mesh
const heights = positions.map(p => p[2]);
const newMinHeight = Math.min(...heights);
const newMaxHeight = Math.max(...heights);
const newQuantizedVertices = new Uint16Array(positions.length * 3);
positions.forEach((p, i) => {
const lonRad = Cesium.Math.toRadians(p[0]);
newQuantizedVertices[i] = Math.round(
Cesium.Math.lerp(MAX_SHORT, 0, (lonRad - tileRectangle.east) / (tileRectangle.west - tileRectangle.east))
);
const latRad = Cesium.Math.toRadians(p[1]);
newQuantizedVertices[i + positions.length] = Math.round(
Cesium.Math.lerp(MAX_SHORT, 0, (latRad - tileRectangle.north) / (tileRectangle.south - tileRectangle.north))
);
const relativeHeight = Math.round(
Cesium.Math.lerp(0, MAX_SHORT, (p[2] - newMinHeight) / (newMaxHeight - newMinHeight))
);
newQuantizedVertices[i + positions.length * 2] = relativeHeight;
});
data._minimumHeight = newMinHeight;
data._maximumHeight = newMaxHeight;
data._quantizedVertices = newQuantizedVertices;
return data as TerrainData;
}
}
A few notes:
- I removed some of the code because it was specific to my app, not the general solution to the problem I described in this post (So copy-pasting it probably won’t work
).
- An additional argument is passed to the constructor - a list of
ModelEdit
s which are polygons the user created and wants to be edited. In addition, every polygon is passed with a list of triangles that is the result of the TIN algorithm on that polygon.
- To make this work, every time the user creates a new model edit you should remove and destroy the previous terrain and add a new one, with the new model edit (in addition to any previous ones).
- I added a TODO in the middle of the code to note that this solution is not ideal. It doesn’t break the mesh’s triangles into smaller ones, just changes the elevation of the existing triangles’ points. This causes issues if the user’s polygon intersects any mesh triangles. This is mostly noticeable when trying to edit elements such as bridges, but for our use cases it was fine since the user mostly wants to get rid of holes or small mounds.
If anyone has any questions feel free to ask 
Shay