需求
最近在项目开发中遇到一个需求,需要将Cesium加载的地块瓦片服务贴到3dtiles倾斜摄影模型上。
思路
结合网上查询的相关资料,初步确定了四种实现思路:
- 修改imageryProvider使其支持贴地 (技术难度大)
- 通过WFS服务空间查询获取3dtiles范围的矢量要素渲染 (性能开销太大,严重卡顿)
- 通过3dtiles范围裁剪WMS服务,设置rectangle纹理为裁剪的图片实现贴地 ( 最终方案 )
- 直接给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
| const blockServiceUrl = gis_config.wms_services.find((item: any) => item.name === '地块').url const layerName = gis_config.wms_services.find((item: any) => item.name === '地块').layerName
3dtilesets.forEach((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); 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}` ) .then((res) => res.blob()) .then((blob) => { return new Promise((resolve, reject) => { var img = new Image(); img.onload = () => resolve(img); img.onerror = reject; img.src = URL.createObjectURL(blob); }); }) .then((image) => { const primitive = new Cesium.GroundPrimitive({ geometryInstances: new Cesium.GeometryInstance({ geometry: new Cesium.RectangleGeometry({ rectangle }) }), appearance: new Cesium.Appearance({ translucent: true, material: new Cesium.Material({ fabric: { type: 'Image', uniforms: { 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
| function add3dtileVectorFeatures() { const unionMultiPolygon = turf.union({ type: 'FeatureCollection', features: regionOf3Dtilesets }); if (!unionMultiPolygon) return; const gmlMultiPolygon = multiPolygonToGml(unionMultiPolygon); const workSpace = 'your_name_space' 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;
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, allowPicking: false }); viewer.scene.primitives.add(primitive); }); }
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; }
|