需求

最近在项目开发中遇到一个需求,需要将Cesium加载的地块瓦片服务贴到3dtiles倾斜摄影模型上。

思路

结合网上查询的相关资料,初步确定了四种实现思路:

  1. 修改imageryProvider使其支持贴地 (技术难度大)
  2. 通过WFS服务空间查询获取3dtiles范围的矢量要素渲染 (性能开销太大,严重卡顿)
  3. 通过3dtiles范围裁剪WMS服务,设置rectangle纹理为裁剪的图片实现贴地 ( 最终方案 )
  4. 直接给3dtiles模型叠加一层图片材质; ( 较为复杂 )

其中1和4的思路没有去实际验证,有能力且有兴趣的朋友可以尝试一下。我验证了2和3的方案,其中第2方案加载后导致操作严重卡顿,性能太差放弃了,最终选用了第3个方案。

关键代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
//通过config文件获取wms服务的url和图层名
const blockServiceUrl = gis_config.wms_services.find((item: any) => item.name === '地块').url
const layerName = gis_config.wms_services.find((item: any) => item.name === '地块').layerName

//遍历需要被wms服务覆盖的3dtileset对象
3dtilesets.forEach((tileset) => {
//通过tileset的包围球计算包围矩形
const rectangle = Cesium.Rectangle.fromBoundingSphere(tileset.boundingSphere)
const west = Cesium.Math.toDegrees(rectangle.west);
const south = Cesium.Math.toDegrees(rectangle.south);
const east = Cesium.Math.toDegrees(rectangle.east);
const north = Cesium.Math.toDegrees(rectangle.north);

//通过bbox参数获取3dtileset范围的WMS影像图片,通过Width和Height指定分辨率
fetch(
`${blockServiceUrl}?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&FORMAT=image%2Fpng&TRANSPARENT=true&STYLES&LAYERS=${layerName}&exceptions=application%2Fvnd.ogc.se_inimage&SRS=EPSG%3A4326&WIDTH=1024&HEIGHT=1024&BBOX=${west},${south},${east},${north}`
)
//将获取到的影像数据转换为Blob对象
.then((res) => res.blob())

//将Blob对象转换为Image对象
.then((blob) => {
//将请求到的图片保存为Image对象
return new Promise((resolve, reject) => {
var img = new Image();
img.onload = () => resolve(img);
img.onerror = reject;
img.src = URL.createObjectURL(blob);
});
})
.then((image) => {
//通过GroundPrimitive创建矩形实现贴倾斜摄影
const primitive = new Cesium.GroundPrimitive({
geometryInstances: new Cesium.GeometryInstance({
geometry: new Cesium.RectangleGeometry({
rectangle
})
}),
appearance: new Cesium.Appearance({
translucent: true,
//使用请求到的WMS图片作为矩形的纹理
material: new Cesium.Material({
fabric: {
type: 'Image',
uniforms: {
image //使用上面的image
}
}
})
})
});

//将创建的矩形添加到场景中
viewer.scene.primitives.add(primitive);
});
});

补充

方案2的关键实现代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
//加载3dtiles范围的矢量地块(补充被遮盖的wms地块);
function add3dtileVectorFeatures() {
//计算融合多边型集合
const unionMultiPolygon = turf.union({
type: 'FeatureCollection',
features: regionOf3Dtilesets
});
if (!unionMultiPolygon) return;

//范围polygon转gml格式
const gmlMultiPolygon = multiPolygonToGml(unionMultiPolygon);

//wfs查询与3dtileset范围面相交的地块矢量数据
const workSpace = 'your_name_space' //geoserver图层服务的工作空间
const layerName = workSpace+':your_layer_name';
const geometryKey = 'the_geom'
fetch(`/geoserver/${workSpace}/ows`, {
method: 'POST',
headers: {
'Content-Type': 'text/xml'
},
body: `
<wfs:GetFeature service="WFS" version="1.1.0" outputFormat="application/json" xmlns:wfs="http://www.opengis.net/wfs" xmlns:ogc="http://www.opengis.net/ogc" xmlns:gml="http://www.opengis.net/gml">
<wfs:Query typeName="${layerName}">
<ogc:Filter>
<Intersects>
<PropertyName>${geometryKey}</PropertyName>
${gmlMultiPolygon}
</Intersects>
</ogc:Filter>
</wfs:Query>
</wfs:GetFeature>
`
})
.then((res) => res.json())
.then(async (featureCollection: GeoJSON.FeatureCollection) => {
//渲染矢量要素
const dataSource = await Cesium.GeoJsonDataSource.load(featureCollection);
const geometryInstances = dataSource.entities.values
.filter((item) => item.polygon && item.polygon.hierarchy)
.map((item) => {
const {
properties: {
LAND_TYPE: { _value: landType }
}
} = item;
//计算颜色
if (landType) {
let colorString = undefined;
switch (landType) {
case '01':
colorString = '#2897f9';
break;
case '02':
colorString = '#fda861';
break;
case '03':
colorString = '#00b048';
break;
case '04':
colorString = '#b85a9e';
break;
}
const fillColor = colorString
? Cesium.Color.fromCssColorString(colorString).withAlpha(0.5)
: Cesium.Color.BLACK;

//创建Primitive几何实例
return new Cesium.GeometryInstance({
geometry: new Cesium.PolygonGeometry({
polygonHierarchy: item.polygon!.hierarchy!._value
}),
attributes: {
color: Cesium.ColorGeometryInstanceAttribute.fromColor(fillColor)
}
});
}
});
const primitive = new Cesium.GroundPrimitive({
geometryInstances: geometryInstances,
appearance: new Cesium.PerInstanceColorAppearance({
translucent: true
}),
compressVertices: true,
//releaseGeometryInstances: true,
allowPicking: false
});
viewer.scene.primitives.add(primitive);
});
}

//multipolygon转gml函数
export function multiPolygonToGml(multiPolygon: GeoJSON.Feature<GeoJSON.MultiPolygon>, srsCode?: number) {
const multipolygon = multiPolygon.geometry.coordinates;
const srsCodeParam = srsCode ? srsCode : 4326;

let gmlXml = `<gml:MultiPolygon srsName="EPSG:${srsCodeParam}">\n`;
multipolygon.forEach((polygonCoords: any) => {
gmlXml += ` <gml:polygonMember>\n`;
gmlXml += ` <gml:Polygon>\n`;
gmlXml += ` <gml:exterior>\n`;
gmlXml += ` <gml:LinearRing>\n`;
gmlXml += ` <gml:coordinates>`;
const coordinates = polygonCoords[0].map((coord: number[]) => `${coord[0]},${coord[1]}`).join(' ');
gmlXml += `${coordinates}</gml:coordinates>\n`;
gmlXml += ` </gml:LinearRing>\n`;
gmlXml += ` </gml:exterior>\n`;
gmlXml += ` </gml:Polygon>\n`;
gmlXml += ` </gml:polygonMember>\n`;
});
gmlXml += `</gml:MultiPolygon>\n`;

return gmlXml;
}