显示投影几何

字数统计: 3.6k
阅读时长: 约 10 分钟
当前版本: 4.29

了解在不同投影下几何的显示。

几何投影变换会将几何形状的节点从一个坐标系(空间参考)变换到另一坐标系。例如,地理坐标系(如 WGS84 (4326))投影到投影坐标系(如 World Sinusoidal (54008))。

每个投影包含以下参数之一:面积、角度或方向。根据应用程序的要求选择对应的坐标系。例如,如果数据是以北极为中心,则通常不会使用 Web Mercator (3857) 空间参考,因为此投影无法正确表示极点要素,存在大面积失真。可以使用 North Pole Gnomonic (102034) 空间参考,因为它保真了北极周围的区域。

在本教程中,我们使用投影引擎,展示列表中选择不同的空间参考来投影 GeoJSON 要素。地图中有一个中心点和缓冲区图形的示例图形。

先决条件

此教程没有先决条件。

步骤

创建新 Pen

  1. 转至 CodePen,为您的制图应用程序创建新 Pen。

添加 HTML 元素

  1. CodePen > HTML 中,添加 HTML 和 CSS,创建包含 viewDivwkid 元素的页面。viewDiv 元素用于显示地图,设置其宽高,在浏览器中满屏显示。wkid 设置为 Web MercatorWGS84。创建 <script><link> 标签,引入 CSS 和 JS 库。

    注: CodePen 中是不需要输入 <!DOCTYPE html> 标签的。但如果您要在其他编辑器或是本地服务器上运行页面,需要手动在 HTML 页面顶部添加该标签。

    html
    
    <html>
        <head>
            <meta charset="utf-8" />
            <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
            <title>Display projected geometries</title>
            <style>
                html,
                body,
                #viewDiv {
                    padding: 0;
                    margin: 0;
                    height: 100%;
                    width: 100%;
                    background-color: \#ffffff;
                }
                #wkid {
                    position: absolute;
                    top: 15px;
                    right: 15px;
                    width: 220px;
                    font-family: "Avenir Next";
                    font-size: 1em;
                }
            </style>
            <link rel="stylesheet" href="https://js.geoscene.cn/4.29/geoscene/themes/light/main.css" />
            <script src="https://js.geoscene.cn/4.29"></script>
    
        </head>
        <body>
            <div id="viewDiv"></div>
            <select id="wkid" class="geoscene-widget geoscene-select">
    
                <option value="3857" disabled>Select a projection</option>
                <optgroup label="Equidistant (maintain length)">
                    <option value="4326" selected>WGS84 (GCS) -> pseudo Plate Carrée (Cylindrical)</option>
    
                </optgroup>
    
                <optgroup label="Compromise (distort all)">
                    <option value="3857">Web Mercator Auxiliary Sphere (Cylindrical)</option>
    
                </optgroup>
            </select>
        </body>
    </html>

添加模块

  1. require 语句中,添加模块。

    更多信息

    GeoScene Maps SDK for JavaScript 提供了 AMD 模块ES 模块,本教程中我们是以 AMD 为例。指定 "geoscene/Map" 来加载 Map 模块。加载模块后,它们将作为参数 (例如,Map) 传递给回调函数,以便在应用程序中使用。保持模块引用和回调参数的顺序相同是很重要的。有关不同类型模块的更多信息,请访问工具指南主题。

    html
    <script>
        require([
            "geoscene/views/MapView",
            "geoscene/Map",
            "geoscene/request",
            "geoscene/geometry/SpatialReference",
            "geoscene/Graphic",
            "geoscene/geometry/Point",
            "geoscene/geometry/Polyline",
            "geoscene/layers/GeoJSONLayer",
            "geoscene/geometry/geometryEngine",
            "geoscene/geometry/projection",
        ], (
            MapView,
            Map,
            geosceneRequest,
            SpatialReference,
            Graphic,
            Point,
            Polyline,
            GeoJSONLayer,
            geometryEngine,
            projection
        ) => {
    
        });
    </script>

定义全局变量和样式

定义一个代表空间参考的全局变量。定义一个多边形和一个点样式,在后续步骤中用于创建缓冲区样式。

  1. 定义 spatialReferenceview 变量。

    js
            "geoscene/views/MapView",
            "geoscene/Map",
            "geoscene/request",
            "geoscene/geometry/SpatialReference",
            "geoscene/Graphic",
            "geoscene/geometry/Point",
            "geoscene/geometry/Polyline",
            "geoscene/layers/GeoJSONLayer",
            "geoscene/geometry/geometryEngine",
            "geoscene/geometry/projection",
        ], (
            MapView,
            Map,
            geosceneRequest,
            SpatialReference,
            Graphic,
            Point,
            Polyline,
            GeoJSONLayer,
            geometryEngine,
            projection
        ) => {
    
            let spatialReference;
            let view;
  2. 设置在地图上缓冲点时将显示的面和点样式。

    js
            "geoscene/views/MapView",
            "geoscene/Map",
            "geoscene/request",
            "geoscene/geometry/SpatialReference",
            "geoscene/Graphic",
            "geoscene/geometry/Point",
            "geoscene/geometry/Polyline",
            "geoscene/layers/GeoJSONLayer",
            "geoscene/geometry/geometryEngine",
            "geoscene/geometry/projection",
        ], (
            MapView,
            Map,
            geosceneRequest,
            SpatialReference,
            Graphic,
            Point,
            Polyline,
            GeoJSONLayer,
            geometryEngine,
            projection
        ) => {
    
            let spatialReference;
            let view;
    
            const polySym = {
                type: "simple-fill", // autocasts as new SimpleFillSymbol()
                color: [150, 130, 220, 0.85],
                outline: {
                    color: "gray",
                    width: 0.5,
                },
            };
    
            const pointSym = {
                type: "simple-marker", // autocasts as new SimpleMarkerSymbol()
                color: "red",
                outline: {
                    color: "white",
                    width: 0.5,
                },
                size: 5,
            };

添加 GeoJSON 图层

要访问 GeoJSONLayer 类,可基于 GeoJSON 数据创建一个图层。GeoJSON 要素位于 WGS84 地理坐标系中。添加到地图后,要素将自动进行投影以匹配地图视图中的空间参考。要可视化每个空间参考的世界范围,将显示边界图形。

  1. 创建边界图形。设置 symboloutline 属性以创建灰色 dash 线。将几何 type 设置为 extent 并将 spatialReference 属性设置为 WGS84

    js
        const pointSym = {
            type: "simple-marker", // autocasts as new SimpleMarkerSymbol()
            color: "red",
            outline: {
                color: "white",
                width: 0.5,
            },
            size: 5,
        };
    
        const projectionBoundary = {
            symbol: {
                type: "simple-fill",
                color: null,
                outline: {
                    width: 0.5,
                    color: [50, 50, 50, 0.75],
                    style: "dash",
                },
            },
            geometry: {
                type: "extent",
                xmin: -180,
                xmax: 180,
                ymin: -90,
                ymax: 90,
                spatialReference: SpatialReference.WGS84,
            },
        };
  2. 创建实例化 GeoJSONLayer 类的 countriesGeoJson 元素。设置 renderer 以在国家/地区周围添加紫色轮廓。

    js
        const projectionBoundary = {
            symbol: {
                type: "simple-fill",
                color: null,
                outline: {
                    width: 0.5,
                    color: [50, 50, 50, 0.75],
                    style: "dash",
                },
            },
            geometry: {
                type: "extent",
                xmin: -180,
                xmax: 180,
                ymin: -90,
                ymax: 90,
                spatialReference: SpatialReference.WGS84,
            },
        };
    
        const countriesGeoJson = new GeoJSONLayer({
            url: "https://services3.arcgis.com/GVgbJbqm8hXASVYi/ArcGIS/rest/services/World_Countries_(Generalized)/FeatureServer/0/query?where=1%3D1&outFields=\*&f=geojson",
            copyright: "Esri",
            spatialReference: {
                wkid: 4326,
            },
            renderer: {
                type: "simple",
                symbol: {
                    type: "simple-fill",
                    color: [255, 255, 255, 1],
                    outline: {
                        width: 0.5,
                        color: [100, 70, 170, 0.75],
                    },
                },
            },
        });
  3. countriesGeoJson 图层添加到 map

    js
        const countriesGeoJson = new GeoJSONLayer({
            url: "https://services3.arcgis.com/GVgbJbqm8hXASVYi/ArcGIS/rest/services/World_Countries_(Generalized)/FeatureServer/0/query?where=1%3D1&outFields=\*&f=geojson",
            copyright: "Esri",
            spatialReference: {
                wkid: 4326,
            },
            renderer: {
                type: "simple",
                symbol: {
                    type: "simple-fill",
                    color: [255, 255, 255, 1],
                    outline: {
                        width: 0.5,
                        color: [100, 70, 170, 0.75],
                    },
                },
            },
        });
    
        const map = new Map({
            layers: [countriesGeoJson],
        });

设置空间参考

GeoJSON 图层的默认空间参考是 WGS84 (4326)。下拉菜单中在 WGS84 和 Web Mercator (3857) 之间切换。

  1. 创建 getSpatialReference 函数,该函数将基于从选择器中选择的 wkid 返回 SpatialReference 类的实例。

    js
        const map = new Map({
            layers: [countriesGeoJson],
        });
    
        function getSpatialReference(wkid) {
            return new SpatialReference({
                wkid: wkid,
            });
        }
  2. wkidSelect 变量分配给 wkid HTML 元素。通过调用 getSpatialReference 函数,将 spatialReference 设置为选择器中的 wkid 值。

    js
        const map = new Map({
            layers: [countriesGeoJson],
        });
    
        const wkidSelect = document.getElementById("wkid");
        spatialReference = getSpatialReference(wkidSelect.value);
  3. 添加事件监听器以根据选择器中的 event.target.value 来注册更改。

    js
        const wkidSelect = document.getElementById("wkid");
        spatialReference = getSpatialReference(wkidSelect.value);
    
        wkidSelect.addEventListener("change", (event) => {
            spatialReference = getSpatialReference(event.target.value);
    
        });

在坐标系之间切换

每次选择新的空间参考时,都需要移除并重新创建视图。创建一个函数,该函数将根据所选的空间参考重新投影视图。

  1. 创建 center 点,该点将在每次重新投影时聚焦视图。将 latitude 设置为 30longitude 设置为 -10。将 spatialReference 设置为 4326 (WGS84)。

    js
        wkidSelect.addEventListener("change", (event) => {
            spatialReference = getSpatialReference(event.target.value);
    
        });
    
        let center = new Point({
            latitude: 30,
            longitude: -10,
            spatialReference: {
                wkid:4326,
            },
        });
  2. 创建 createViewWithSpatialReference 函数。

    js
        let center = new Point({
            latitude: 30,
            longitude: -10,
            spatialReference: {
                wkid: 4326,
            },
        });
    
        function createViewWithSpatialReference(){
    
        }
  3. 添加一个条件语句,如果存在 view,则该语句将调用 destroy() 方法。

    js
        function createViewWithSpatialReference(){
    
            if (view) {
                view.map = null;
                view.destroy();
            }
    
        }
  4. 使用 MapView 类的新实例设置 view。使用 map 和所选的 spatialReference 设置 mapspatialReference 属性。使用 center 点设置 center 属性。

    js
        function createViewWithSpatialReference(){
    
            if (view) {
                view.map = null;
                view.destroy();
            }
    
            view = new MapView({
                container: "viewDiv",
                map: map,
                spatialReference: spatialReference,
                center: center, //Need to use projection engine
                scale: 150000000,
            });
    
        }
  5. projectionBoundarywkidSelect 元素添加到 view UI

    js
        view = new MapView({
            container: "viewDiv",
            map: map,
            spatialReference: spatialReference,
            center: center, //Need to use projection engine
            scale: 150000000,
        });
    
        view.graphics.add(projectionBoundary);
        view.ui.add(wkidSelect, "top-right");
  6. 调用 createViewWithSpatialReference 方法以在加载应用程序时显示 GeoJSON 图形。

    js
        let center = new Point({
            latitude: 30,
            longitude: -10,
            spatialReference: {
                wkid: 4326,
            },
        });
    
        createViewWithSpatialReference();
  7. 更新事件监听器,通过基于新的空间参考调用 createViewWithSpatialReference 函数来重新投影 GeoJSON 图层。

    js
            wkidSelect.addEventListener("change", (event) => {
                spatialReference = getSpatialReference(event.target.value);
    
                createViewWithSpatialReference();
    
            });
  8. 运行应用程序。在 WGS84 和 Web Mercator 之间切换,以确保每次都重新创建视图。

添加更多空间参考

根据应用程序的需要,可以从许多空间参考中进行选择。

  1. 查找感兴趣的 WKID。

  2. 更新选择器以添加其他投影坐标系,例如:

    • World Eckert VI (54010)
    • World Sinusoidal (54008)
    • World Fuller (54050)
    html
            <option value="3857" disabled>Select a projection</option>
            <optgroup label="Equidistant (maintain length)">
                <option value="4326" selected>WGS84 (GCS) -> pseudo Plate Carrée (Cylindrical)</option>
    
                <option value="54028">World Cassini (Cylindrical)</option>
                <option value="54027">World Equidistant conic (Conic)</option>
    
            </optgroup>
    
            <optgroup label="Conformal (maintain angles)">
                <option value="54026">World Stereographic (Azimuthal)</option>
            </optgroup>
            <optgroup label="Equal-area (maintain area)">
                <option value="54010">World Eckert VI (Pseudocylindrical)</option>
                <option value="54008">World Sinusoidal (Pseudocylindrical)</option>
            </optgroup>
            <optgroup label="Gnomonic (distances)">
                <option value="102034">North Pole Gnomonic (Azimuthal)</option>
            </optgroup>
    
            <optgroup label="Compromise (distort all)">
                <option value="3857">Web Mercator Auxiliary Sphere (Cylindrical)</option>
    
                <option value="54016">World Gall Stereographic (Cylindrical)</option>
                <option value="54042">World Winkel Tripel (Pseudoazimuthal)</option>
                <option value="54050">World Fuller / Dymaxion map (Polyhedral)</option>
  3. 运行应用程序并在新坐标系之间切换。

  4. 检查控制台。将出现一个错误,显示:"#center""incompatible spatialReference {"wkid":4326} with view's spatialReference {"wkid": "THE_SELECTED_WKID"}"

重新投影中心点

您可以在 WGS84 和 Web Mercator 之间进行切换,因为 Web Mercator 的大地坐标由 WGS84 基准面定义。在其他投影坐标系之间切换时,应用程序将失败,因为中心点 [30, -10] 坐标未投影到指定的输出空间参考。默认情况下,视图会自动动态投影几何以匹配地图的空间参考。但是,由于中心点不会作为图形添加到视图中,因此需要使用投影引擎根据从选择器中选择的内容来变换坐标。

  1. createViewWithSpatialReference 函数更新为异步,以便它 awaits projection 引擎 load 引擎的依赖项。

    js
        let center = new Point({
            latitude: 30,
            longitude: -10,
            spatialReference: {
                wkid: 4326,
            },
        });
    
        createViewWithSpatialReference();
    
        async function createViewWithSpatialReference() {
    
            await projection.load();
    
            if (view) {
                view.map = null;
                view.destroy();
            }
    
            view = new MapView({
                container: "viewDiv",
                map: map,
                spatialReference: spatialReference,
                center: center, //Need to use projection engine
                scale: 150000000,
            });
    
            view.graphics.add(projectionBoundary);
            view.ui.add(wkidSelect, "top-right");
    
        }
  2. 基于从选择器中选择的 spatialReference,在 center 点上调用 project 方法。

    js
        async function createViewWithSpatialReference() {
    
        await projection.load();
    
        center = projection.project(center, spatialReference);
  3. 重新运行应用程序,并从选择器中选择不同的空间参考。该应用程序将正常工作,因为中心点坐标随每个选择进行投影。

查看投影坐标

要可视化重投影 center 点的效果,可将其作为图形添加到视图中,然后从新的空间参考显示其 x/y 坐标。

  1. 创建具有 point 参数的 displayCoordinates 函数。定义 popupTemplate 以显示 pointwkidxy 坐标。设置 dockOptionsvisibleElements 以防止用户关闭弹出窗口。

    js
        function getSpatialReference(wkid) {
            return new SpatialReference({
                wkid: wkid,
            });
        }
    
        function displayCoordinates(point) {
    
            const popupTemplate = {
                title: `WKID: ${point.spatialReference.wkid}`,
                content: `<b>X:</b> ${point.x.toFixed(5)}| <b>Y:</b> ${point.y.toFixed(
                5
                )}`,
                overwriteActions: true,
            };
            view.popup.dockOptions = {
                buttonEnabled: false,
            };
            view.popup.visibleElements = {
                closeButton: false,
                featureNavigation: false,
            };
        }
  2. 创建 graphic。使用 point 设置 geometry,并使用上一步中定义的 popupTemplate 设置 popupTemplate。将 graphic 添加至 view,并调用 open 方法以在应用程序加载时显示弹出窗口。

    js
    
        function displayCoordinates(point) {
    
            const popupTemplate = {
                title: `WKID: ${point.spatialReference.wkid}`,
                content: `<b>X:</b> ${point.x.toFixed(5)}| <b>Y:</b> ${point.y.toFixed(
                5
                )}`,
                overwriteActions: true,
            };
            view.popup.dockOptions = {
                buttonEnabled: false,
            };
            view.popup.visibleElements = {
                closeButton: false,
                featureNavigation: false,
            };
    
            const graphic = new Graphic({
                geometry: point,
                popupTemplate: popupTemplate,
            });
            view.graphics.add(graphic);
            view.openPopup({
                features: [graphic],
            });
        }
  3. 更新 createViewWithSpatialReference 函数。when 加载视图时,调用 displayCoordinates 函数,其中 center 点为其参数。

    js
            view = new MapView({
                container: "viewDiv",
                map: map,
                spatialReference: spatialReference,
                center: center, //Need to use projection engine
                scale: 150000000,
            });
    
            view.graphics.add(projectionBoundary);
            view.ui.add(wkidSelect, "top-right");
    
            view.when(() => {
                displayCoordinates(center);
            });
  4. 运行应用程序。在空间参考之间切换以查看中心点的投影坐标。

查看投影的效果

每个投影都保持一个维度的精度,但在另一个维度中会产生不准确性。例如,您可能能够保持面积,但不能保持距离。要查看每个空间参考对圆形形状的影响,请在其中创建一个测地线缓冲区,您可在其中移动鼠标。geodesicBuffer 方法仅适用于 WGS84 (4326) 和 Web Mercator (3857) 空间参考。要在另一个空间参考中查看缓冲区,需要先将点重新投影到 4326 或 3857,然后调用 geodesicBuffer 方法。

  1. 创建 bufferPoint 函数,该函数使用 pointview 作为其参数。如果 point 位于另一空间参考中,则调用 projection 模块并将坐标转换为 4326 以创建缓冲区。如果没有 point,则 return

    js
            function displayCoordinates(point) {
    
                const popupTemplate = {
                    title: `WKID: ${point.spatialReference.wkid}`,
                    content: `<b>X:</b> ${point.x.toFixed(5)}| <b>Y:</b> ${point.y.toFixed(
                    5
                    )}`,
                    overwriteActions: true,
                };
                view.popup.dockOptions = {
                    buttonEnabled: false,
                };
                view.popup.visibleElements = {
                    closeButton: false,
                    featureNavigation: false,
                };
    
                const graphic = new Graphic({
                    geometry: point,
                    popupTemplate: popupTemplate,
                });
                view.graphics.add(graphic);
                view.openPopup({
                    features: [graphic],
                });
    
            }
    
            function bufferPoint(point) {
                if ([3857, 4326].indexOf(point.spatialReference.wkid) === -1) {
                    point = projection.project(point, getSpatialReference(4326));
                    if (!point) {
                        return;
                    }
                }
            }
  2. 创建 buffer 元素以在 point 上调用 geodesicBuffer 方法。它将以 1000 kilometers 为半径缓冲 point

    js
            function bufferPoint(point) {
                if ([3857, 4326].indexOf(point.spatialReference.wkid) === -1) {
                    point = projection.project(point, getSpatialReference(4326));
                    if (!point) {
                        return;
                    }
                }
    
                const buffer = geometryEngine.geodesicBuffer(point, 1000, "kilometers");
    
            }
  3. view 中移除现有 graphics,地图投影边界和中心点图形除外。

    js
            if (point && buffer) {
    
                // Avoid removing the map projection boundary
                view.graphics.removeMany([
                    view.graphics.getItemAt(2),
                    view.graphics.getItemAt(3),
                ]);
    
            }
  4. 创建 bufferGraphic,将 buffer 作为其 geometry,将 polySym 作为其 symbol 样式。将 bufferGraphic 以及通过移动鼠标创建的 point 及其 pointSym 样式添加到 view 中。

    js
            if (point && buffer) {
    
                // Avoid removing the map projection boundary
                view.graphics.removeMany([
                    view.graphics.getItemAt(2),
                    view.graphics.getItemAt(3),
                ]);
    
                const bufferGraphic = {
                    geometry: buffer,
                    symbol: polySym,
                };
                view.graphics.addMany([
                    bufferGraphic,
                    {
                    geometry: point,
                    symbol: pointSym,
                    },
                ]);
    
            }
  5. 创建 createBuffer 函数,该函数将 eventview 作为其参数。根据 event 中的 xy 坐标定义 point。如果有 point,则调用 bufferPoint 函数。

    js
            const graphic = new Graphic({
                geometry: point,
                popupTemplate: popupTemplate,
            });
            view.graphics.add(graphic);
            view.openPopup({
                features: [graphic],
            });
    
        }
    
        function createBuffer(event) {
            let point = view.toMap({
                x: event.x,
                y: event.y,
            });
            if (point) {
                bufferPoint(point);
            }
        }
  6. 创建一个事件处理程序,该处理程序将根据鼠标的移动来缓冲点。

    js
        view = new MapView({
            container: "viewDiv",
            map: map,
            spatialReference: spatialReference,
            center: center, //Need to use projection engine
            scale: 150000000,
        });
    
        view.graphics.add(projectionBoundary);
        view.ui.add(wkidSelect, "top-right");
    
        view.when(() => {
            displayCoordinates(center);
        });
    
        view.on("pointer-move", (event) => {
            createBuffer(event);
        });

运行应用程序

CodePen 中,运行代码以显示地图。

运行应用程序时,您将看到中心点及其坐标。选择新空间参考时,GeoJSON 图层中的要素以及中心点和缓冲区的几何将被重新投影。在地图上移动鼠标以查看每个空间参考的失真。

下一步是什么?

要了解如何使用其他API 功能,请参阅以下教程: