ArcGIS JSAPI 学习教程 - 场景可视区域(SceneView visibleArea)显示以及过滤要素应用

ArcGIS JSAPI 高级教程 - 场景可视区域(SceneView visibleArea)显示以及过滤要素应用

本文主要介绍一下场景可视区域(SceneView visibleArea),一般用于过滤视野内可见部分,实际为相机视锥体在地面上的近似投影多边形。

首先介绍一下实现过程:

1. 主场景称为场景,右上角场景称为类鹰眼场景。

2. 创建包含可视区域的场景,并且添加地形图层,用于计算类鹰眼展示相机高度。

3. 添加要素图层,用于实现过滤功能。

4. 通过场景相机构建类鹰眼场景中的几何体、范围、虚拟相机对象等。

5. 通过监听事件,进行数据更新、要素过滤更新等。

本文包括 完整代码以及在线示例。


完整代码

html 复制代码
<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8"/>
    <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no"/>
    <title>可视区域 | Sample | ArcGIS Maps SDK for JavaScript 4.33</title>
    <style>
        html,
        body,
        #viewDiv {
            padding: 0;
            margin: 0;
            height: 100%;
            width: 100%;
        }

        #viewDivSupport {
            width: 25%;
            height: 35%;
            top: 5px;
            right: 20px;
            border: 2px solid black;
            display: none;
        }

        :root {
            --my-width: 100vw;
        }

        #divToggle {
            display: none;
            bottom: 0;
            right: 5px;
        }

        .white-text {
            color: white;
            line-height: 0;
        }

        #myCustomGroup {
            position: absolute;
            top: 16px;
            left: 64px;
        }

    </style>

    <script type="module" src="https://openlayers.vip/arcgis_api/calcite-components/2.8.1/calcite.esm.js"></script>

    <!-- 引入ArcGIS JS API样式和脚本 -->
    <link rel="stylesheet" href="https://openlayers.vip/arcgis_api/4.33/esri/themes/light/main.css"/>
    <script src="https://openlayers.vip/arcgis_api/4.33/init.js"></script>
    <script>
        var _hmt = _hmt || [];
        (function () {
            var hm = document.createElement("script");
            hm.src = "https://hm.baidu.com/hm.js?f80a36f14f8a73bb0f82e0fdbcee3058";
            var s = document.getElementsByTagName("script")[0];
            s.parentNode.insertBefore(hm, s);
        })();
    </script>
</head>

<body>

<div id="viewDiv">
    <div id="viewDivSupport"></div>

    <div id="divToggle">
        <calcite-label scale="s" layout="inline">
            <p class="white-text">2D</p>
            <calcite-switch id="3dToggle" scale="s"></calcite-switch>
            <p class="white-text">3D</p>
        </calcite-label>
    </div>
</div>

<div id="myCustomGroup">
    <calcite-block open heading="是否显示场景范围" id="renderNodeUI">
        <calcite-label layout="inline">
            关闭
            <calcite-switch id="renderNodeToggle" checked></calcite-switch>
            开启
        </calcite-label>
    </calcite-block>
    <calcite-tile-group id="footprintSelectTileGroup" alignment="center">
        <calcite-tile class="tile-container" heading="模拟建筑数量:">

            <div slot="content-bottom" class="tile-content">
                <calcite-chip></calcite-chip>
            </div>
        </calcite-tile>
    </calcite-tile-group>
</div>

<script>
    require([
        "esri/layers/FeatureLayer",
        "esri/layers/GraphicsLayer",
        "esri/Graphic",
        "esri/core/reactiveUtils",
        "esri/geometry/Polygon",
        "esri/views/SceneView",
        "esri/geometry/Polyline",
        "esri/core/promiseUtils",
        "esri/symbols/IconSymbol3DLayer",
        "esri/symbols/PointSymbol3D",
        "esri/symbols/ObjectSymbol3DLayer",
        "esri/geometry/Point",
        "esri/Map",

        "esri/smartMapping/renderers/color",

    ], (
        FeatureLayer,
        GraphicsLayer,
        Graphic,
        reactiveUtils,
        Polygon,
        SceneView,
        Polyline,
        promiseUtils,
        IconSymbol3DLayer,
        PointSymbol3D,
        ObjectSymbol3DLayer,
        Point,
        Map,
        colorRendererCreator,
    ) => {

        const map = new Map({
            ground: "world-elevation"
        });

        // 创建场景
        const view = new SceneView({
            container: "viewDiv",
            map: map,
            camera: {
                position: [7.95442341, 46.48978665, 3407.29792],
                heading: 351.99,
                tilt: 18.52
            }
        });

        // 控制开启范围显示
        let activateExtent = true;
        const renderNodeToggle = document.getElementById("renderNodeToggle");
        renderNodeToggle.addEventListener("calciteSwitchChange", () => {
            activateExtent = !activateExtent;
        });

        // 添加要素图层,统计展示过滤数量
        const featureLayer = new FeatureLayer({
            url: 'https://gs3d.geosceneonline.cn/server/rest/services/Hosted/ShangHaiBuilding/FeatureServer/0',
            minScale: 0,
            maxScale: 0,
            outFields: ["*"],
        })

        map.add(featureLayer);  // adds the layer to the map

        // visualization based on field and normalization field
        let colorParams = {
            layer: featureLayer,
            view: view,
            field: "SHAPE__Area",
            classificationMethod: "natural-breaks",
            valueExpression: "$feature.SHAPE__Area",
            theme: "high-to-low",
            numClasses: 10,
            symbolType: '3d-volumetric',
        };

        // when the promise resolves, apply the renderer to the layer
        colorRendererCreator.createClassBreaksRenderer(colorParams)
            // colorRendererCreator.createContinuousRenderer(colorParams)
            .then(function (response) {
                featureLayer.renderer = response.renderer;
            });


        // Keep track of the type of supportView
        let isSupportViewTilted = false;

        const cameraIconUrl = "https://openlayers.vip/examples/resources/cameraIcon.svg";
        const camera3DObjectUrl = "https://openlayers.vip/examples/resources/cameraObject.glb";

        view.when(function () {
            view.extent = featureLayer.fullExtent;

            // 创建类鹰眼场景
            const supportView = new SceneView({
                container: "viewDivSupport",
                map: new Map(),
                center: view.camera.position,
                zoom: 13,
                ui: {
                    components: []
                },
                constraints: {
                    tilt: {
                        max: 0 // Prevent the user from tilting the camera. This ensures the view remains a bird's-eye view (top-down), looking straight down
                    }
                }
            });

            // Add the support view to the main view ui and make it visible
            const supportViewElement = document.getElementById("viewDivSupport");
            view.ui.add(supportViewElement, "manual");
            supportViewElement.style.display = "flex";

            // Add a view mode toggle to the supportView for switching between top-down and tilted perspectives
            const divToggle = document.getElementById("divToggle");
            supportView.ui.add(divToggle, "manual");
            const toggle = document.getElementById("3dToggle");
            toggle.addEventListener("calciteSwitchChange", (event) => {
                handle3DToggle(event);
            });

            // 视椎体图层
            const frustumGraphicsLayer = new GraphicsLayer({
                elevationInfo: {
                    mode: "relative-to-scene"
                },
                visible: false
            });

            supportView.map.add(frustumGraphicsLayer);

            // 可见区域图层
            const visibleAreaGraphicsLayer = new GraphicsLayer({
                elevationInfo: {
                    mode: "on-the-ground"
                }
            });

            supportView.map.add(visibleAreaGraphicsLayer);

            // 虚拟相机图层
            const cameraGraphicLayer = new GraphicsLayer({
                elevationInfo: {
                    mode: "on-the-ground"
                }
            });

            supportView.map.add(cameraGraphicLayer);

            // 相机对象
            const cameraGraphic = new Graphic({
                geometry: view.camera.position,
                symbol: new PointSymbol3D({
                    symbolLayers: [
                        new IconSymbol3DLayer({
                            resource: {href: cameraIconUrl},
                            angle: view.camera.heading - 90
                        })
                    ]
                })
            });

            cameraGraphicLayer.add(cameraGraphic);

            // Wait until the support view is ready
            supportView.when(async () => {
                // Show the top-down/tilted view toggle
                divToggle.style.display = "flex";

                // Wait for the layerView of the tree feature layer
                const featureLayerView = await view.whenLayerView(featureLayer);

                // Debounce the queries to the tree featureLayerView to avoid multiple
                // requests being sent to the server while the user is interacting with the Scene
                const debounceQueryTrees = promiseUtils.debounce(async () => {
                    try {

                        // 过滤要素
                        const featureSet = await featureLayerView.queryFeatures({
                            // 使用可见区域过滤
                            geometry: view.visibleArea,
                            returnGeometry: false,
                            outFields: ["floor"]
                        });

                        // 更新显示数量
                        const chip = document.querySelector("calcite-chip");
                        if (chip) {
                            chip.textContent = featureSet.features.length.toString();
                        }

                    } catch (error) {
                        console.error("query failed: ", error);
                    }
                });

                // 监听数据更新
                reactiveUtils.when(
                    () => !featureLayerView.dataUpdating,
                    () => {
                        debounceQueryTrees().catch((error) => {
                            if (error.name === "AbortError") {
                                return;
                            }
                            console.error(error);
                        });
                    }
                );


                // 显示区域样式
                const visibleAreaSymbol = {
                    type: "polygon-3d",
                    symbolLayers: [
                        {
                            type: "fill",
                            material: {color: "white"},
                            outline: {color: "white", width: 2},
                            pattern: {
                                type: "style",
                                style: "forward-diagonal"
                            },
                        },
                    ]
                };

                // 获取可视区域
                function getVisibleAreaGraphics(visibleArea, extent) {

                    const parts = visibleArea.rings.map(
                        (ring) => new Polygon({rings: [ring], spatialReference: visibleArea.spatialReference})
                    );

                    const visibleAreaPolygons = parts.map((part) => new Graphic({
                        geometry: part,
                        symbol: visibleAreaSymbol
                    }));

                    // 显示地图范围
                    if (activateExtent && extent) {

                        // extentBuilding
                        const polygonExtent = Polygon.fromExtent(extent)
                        polygonExtent.hasZ = false;

                        const polygonExtentGraphic = new Graphic({
                            geometry: polygonExtent,
                            symbol: {
                                type: "simple-fill",
                                color: [51, 51, 204, 0],
                                style: "solid",
                                outline: {
                                    color: [0, 255, 255, 1],
                                    width: 2
                                }
                            }
                        });
                        visibleAreaPolygons.push(polygonExtentGraphic);

                    }

                    return visibleAreaPolygons;
                }


                // 地形取样
                let elevationSampler = view.groundView.elevationSampler;

                // 监听可视区域变化
                reactiveUtils.when(
                    () => view.visibleArea,
                    () => {
                        // Debounce the queries to the tree featureLayerView to avoid multiple requests
                        debounceQueryTrees().catch((error) => {
                            if (error.name === "AbortError") {
                                return;
                            }
                            console.error(error);
                        });
                        // Update the supportView
                        updateSceneView();
                    },
                    {
                        initial: true
                    }
                );


                // 监听场景更新
                reactiveUtils.when(
                    () => !view.updating,
                    () => {
                        supportView.goTo({
                            // goTo the visibleArea changing the tilt for a better camera positioning
                            target: [view.visibleArea.extent],
                            tilt: isSupportViewTilted ? 60 : 0
                        });
                    }
                );

                // 更新类鹰眼场景显示数据
                async function updateSceneView() {
                    frustumGraphicsLayer.removeAll();
                    visibleAreaGraphicsLayer.removeAll();

                    let {visibleArea, extent} = view;
                    const visibleAreaGraphics = getVisibleAreaGraphics(visibleArea, extent);
                    visibleAreaGraphicsLayer.addMany(visibleAreaGraphics);

                    // Get the elevation value from the elevationSampler starting from the camera position
                    const cameraPosition = view.camera.position;
                    const groundElevation = elevationSampler.elevationAt(cameraPosition.x, cameraPosition.y);
                    const relativeCameraHeight = cameraPosition.z - groundElevation;

                    // 更新相机姿态
                    cameraGraphic.geometry = new Point({
                        x: cameraPosition.x,
                        y: cameraPosition.y,
                        z: relativeCameraHeight,
                        spatialReference: view.visibleArea.spatialReference
                    });

                    cameraGraphic.symbol = updateCameraSymbol(view.camera.heading, view.camera.tilt);

                    // 可视区域样式
                    const lineSymbol = {
                        type: "simple-line",
                        color: [255, 255, 255],
                        width: 1.5,
                    };

                    // Iterate on the visibleArea rings
                    for (const ring of visibleArea.rings) {
                        for (const point of ring) {
                            // Create a Polyline connecting the camera position to visibleArea vertices
                            const connectingLine = new Polyline({
                                paths: [
                                    [
                                        [point[0], point[1], 0],
                                        [cameraPosition.x, cameraPosition.y, relativeCameraHeight]
                                    ]
                                ],
                                spatialReference: visibleArea.spatialReference
                            });

                            // Create a Graphic for the connecting line and add it its graphicsLayer
                            const lineGraphic = new Graphic({
                                geometry: connectingLine,
                                symbol: lineSymbol
                            });
                            frustumGraphicsLayer.add(lineGraphic);
                        }
                    }
                }
            });

            // 切换类鹰眼场景二三维效果
            function handle3DToggle(event) {
                isSupportViewTilted = event.target.checked;
                if (isSupportViewTilted) {
                    frustumGraphicsLayer.visible = true;
                    supportView.constraints.tilt = {};
                    cameraGraphicLayer.elevationInfo.mode = "relative-to-scene";
                } else {
                    // supportView is in bird's-eye view mode (top-down)
                    frustumGraphicsLayer.visible = false;
                    supportView.constraints.tilt.max = 0;
                    cameraGraphicLayer.elevationInfo.mode = "on-the-ground";
                }
                // Get the proper symbol for the camera and then rotate it
                cameraGraphic.symbol = updateCameraSymbol(view.camera.heading, view.camera.tilt);

                // Center the supportView to the visibleArea extent
                supportView.goTo({
                    target: [view.visibleArea.extent],
                    tilt: isSupportViewTilted ? 60 : 0
                });
            }

            // 更新相机显示
            function updateCameraSymbol(heading, tilt) {
                if (isSupportViewTilted) {
                    return new PointSymbol3D({
                        symbolLayers: [
                            new ObjectSymbol3DLayer({
                                width: 15,
                                heading,
                                tilt: tilt - 90,
                                resource: {
                                    href: camera3DObjectUrl
                                }
                            })
                        ]
                    });
                } else {
                    // supportView is in bird's-eye view mode (top-down)
                    return new PointSymbol3D({
                        symbolLayers: [
                            new IconSymbol3DLayer({
                                resource: {href: cameraIconUrl},
                                angle: heading - 90
                            })
                        ]
                    });
                }
            }
        })
    });
</script>
</body>
</html>

在线示例

ArcGIS Maps SDK for JavaScript 在线示例:场景可视区域(visibleArea)显示以及过滤要素应用

相关推荐
GIS阵地1 天前
如何利用QGIS提取影像(多波段背景透明)边界,非包围框
arcgis·qgis·开源gis·地理信息系统·osgeo4w
qq_214803291 天前
ArcGIS Runtime与GeoTools融合实践:加密SHP文件的完整读写方案
java·安全·arcgis
Sylus_sui2 天前
Vue3实现PDF目录预览功能
arcgis
2503_928411564 天前
11.5 包和包管理器
数据库·arcgis·node.js·编辑器
受伤的僵尸5 天前
为什么Arcgis/Qgis里我的图是扁的
arcgis
weixin_贾5 天前
ArcGIS+ENVI实战:从零制作生态影响评价专题图(以植被类型与土壤侵蚀为例)
arcgis·土地利用·dem·水文分析·土壤侵蚀·arcgis栅格·envi遥感影像
Q一件事7 天前
Arcgis出图出现横条/条纹问题的解决方案
arcgis
凌然先生8 天前
17.如何利用ArcGIS进行空间统计分析
经验分享·笔记·arcgis·电脑
GIS思维9 天前
ArcGIS图斑属性自动智能填写!告别手动低效输入
arcgis