1. 程式人生 > >arcgis api for js - 圖上標繪及測量

arcgis api for js - 圖上標繪及測量

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no">
    <title>draw38_3.21</title>
    <!--此程式碼進行到,要實現繪圖介面。-->
    <!--<link rel="stylesheet" href="https://js.arcgis.com/3.21/dijit/themes/claro/claro.css">-->
    <!--<link rel="stylesheet" href="https://js.arcgis.com/3.21/esri/css/esri.css">-->
    <!--<script src="https://js.arcgis.com/3.21/"></script>-->
    <!--<script src="https://cdn.bootcss.com/jquery/1.10.2/jquery.min.js"></script>-->

    <!--上下兩個介面都行。-->
    <link rel="stylesheet" href="https://js.arcgis.com/3.26/dijit/themes/nihilo/nihilo.css">
    <link rel="stylesheet" href="https://js.arcgis.com/3.26/esri/css/esri.css">
    <script src="https://js.arcgis.com/3.26/"></script>

    <style>
        html, body, #map {
            height: 100%; width: 100%; margin: 0; padding: 0;
        }
        #search {
            z-index: 20;
            height: 35px;
            width: 300px;
            position: absolute;
            top: 10px;
            left: 0;
            right: 0;
            margin: auto;
        }
        #drawOperations {
            z-index: 20;
            position: absolute;
            top: 60px;
            left: 5px;
            color: #444;
            width: 320px;
            overflow: auto;
            font-family: 仿宋體;
            border: solid 1px #4A92E7;
            border-radius: 4px;
            background-color: #fff;
        }
        /*#info button {*/
            /*width: 30px;*/
            /*height:30px;*/
            /*line-height:30px;*/
            /*text-align: center;*/
            /*background-color: #fff;*/
            /*padding: 2px;*/
            /*margin: 4px;*/
            /*cursor: pointer;*/
            /*border-radius: 3px;*/
            /*border: 1px solid*/
        /*}*/
        /*#info button:hover {*/
            /*border: 1px solid #1e90ff;*/
        /*}*/
        #drawOperations img{
            width:40px;
            margin:4px;
        }
        #drawOperations div{
            padding:4px 4px 0px 4px;
        }
    </style>
    <!--匯入該js才可以新增多行文字。-->
    <script src="js/esri.symbol.MultiLineTextSymbol.js"></script>
    <script>
        require([
            "esri/config",
            "esri/tasks/GeometryService",
            "esri/tasks/LengthsParameters",
            "esri/tasks/AreasAndLengthsParameters",
            "esri/symbols/TextSymbol", "esri/Color", "esri/symbols/Font",
            "esri/map",
            "esri/layers/ArcGISTiledMapServiceLayer",
            "esri/layers/ArcGISDynamicMapServiceLayer",
            "dojo/_base/connect",
            "esri/dijit/BasemapToggle",
            "esri/toolbars/draw",
            "esri/geometry/Point", "esri/geometry/Polyline", "esri/geometry/Polygon",
            "esri/dijit/Search",
            "dijit/registry",
            "esri/dijit/Scalebar",
            "esri/symbols/SimpleMarkerSymbol",
            "esri/symbols/SimpleLineSymbol",
            "esri/symbols/SimpleFillSymbol",
            "esri/symbols/PictureMarkerSymbol",
            "esri/symbols/CartographicLineSymbol",
            "esri/symbols/PictureFillSymbol",
            "esri/geometry/Extent",
            "esri/SpatialReference",
            "esri/geometry/mathUtils",
            "esri/geometry/ScreenPoint",
            "esri/toolbars/edit",
            "dijit/Menu",
            "dijit/MenuItem",
            "dijit/MenuSeparator",
            "esri/graphic",
            "esri/layers/GraphicsLayer",
            "esri/dijit/Popup",
            "esri/dijit/PopupTemplate",
            "esri/InfoTemplate",
            "dojo/dom-construct",
            "dojo/dom",
            "dojo/on",
            "dojo/domReady!"
        ], function(
            esriConfig, GeometryService, LengthsParameters, AreasAndLengthsParameters,
            TextSymbol, Color, Font,
            Map,  ArcGISTiledMapServiceLayer, ArcGISDynamicMapServiceLayer,
            connect, BasemapToggle, Draw, Point, Polyline, Polygon, Search, registry, Scalebar,
            SimpleMarkerSymbol, SimpleLineSymbol, SimpleFillSymbol,
            PictureMarkerSymbol, CartographicLineSymbol, PictureFillSymbol,
            Extent, SpatialReference, mathUtils, ScreenPoint, Edit, Menu, MenuItem,
            MenuSeparator,
            Graphic, GraphicsLayer, Popup, PopupTemplate, InfoTemplate,
            domConstruct,dom, on
        ) {
            //量算
            function doMeasure(geometry) {
                let symbol;
                measureGeometry = geometry;
                draw.deactivate();
                switch (geometry.type) {
                    case "point":
                        symbol = new SimpleMarkerSymbol();
                        break;
                    case "polyline":
                        symbol = new SimpleLineSymbol(SimpleLineSymbol.STYLE_SOLID, new dojo.Color([0, 0, 0]), 5);
                        break;
                    case "polygon":
                        symbol = new SimpleFillSymbol(SimpleFillSymbol.STYLE_NONE, new esri.symbol.SimpleLineSymbol(esri.symbol.SimpleLineSymbol.STYLE_DASHDOT, new dojo.Color([255, 0, 0]), 2), new dojo.Color([255, 255, 0, 0.25]));
                        break;
                }
                //設定樣式
                const graphic = new Graphic(geometry, symbol);

                map.infoWindow.hide();

                graphicsLayer.add(graphic);
                //進行投影轉換,完成後呼叫projectComplete
                MeasureGeometry(geometry);
            }

            //投影轉換完成後呼叫方法
            function MeasureGeometry(geometry) {
                // 如果為點型別,就測量經緯度。
                if(geometry.type == "point"){
                    outputLongitudeLatitude(geometry.x, geometry.y);
                }
                // 如果為線型別就進行lengths距離測算
                else if (geometry.type == "polyline") {
                    const lengthParams = new LengthsParameters();
                    lengthParams.polylines = [geometry];
                    lengthParams.lengthUnit = GeometryService.UNIT_METER;
                    lengthParams.geodesic = true;
                    lengthParams.polylines[0].spatialReference = new SpatialReference(4326);
                    geometryService.lengths(lengthParams);
                    dojo.connect(geometryService, "onLengthsComplete", outputDistance);
                }
                // 如果為面型別,需要先進行simplify操作再進行面積測算
                else if (geometry.type == "polygon") {
                    const areasAndLengthParams = new AreasAndLengthsParameters();
                    areasAndLengthParams.lengthUnit = GeometryService.UNIT_METER;
                    areasAndLengthParams.areaUnit = GeometryService.UNIT_SQUARE_METERS;
                    this.outSR = new SpatialReference({wkid: 102113});
                    geometryService.project([geometry], this.outSR, function (geometry) {
                        geometryService.simplify(geometry, function (simplifiedGeometries) {
                            // 此處把各特徵點的經緯度,轉換成了座標系中的實際座標,
                            // 單位即上邊規定的單位。
                            // console.log('simplifiedGeometries:', simplifiedGeometries);
                            areasAndLengthParams.polygons = simplifiedGeometries;
                            areasAndLengthParams.polygons[0].spatialReference = new SpatialReference(102113);
                            geometryService.areasAndLengths(areasAndLengthParams);
                        });
                    });
                    dojo.connect(geometryService, "onAreasAndLengthsComplete", outputAreaAndLength);
                }

            }
            
            // 顯示經緯度。
            function outputLongitudeLatitude(lon, lat) {
                const curPos = new Point(lon, lat, map.spatialReference);
                let textSymbol =  new TextSymbol(
                    "經度:" + lon.toFixed(3) + "\n緯度:" + lat.toFixed(3)
                ).setColor(new Color([182,0,0])).setFont(
                    new Font("14pt").setWeight(Font.WEIGHT_BOLD)
                ).setHorizontalAlignment("left").setVerticalAlignment('middle');
                graphicsLayer.add(new Graphic(curPos, textSymbol));
            }

            //顯示測量距離
            function outputDistance(result) {
                const curX = measureGeometry.paths[0][measureGeometry.paths[0].length - 1][0];
                const curY = measureGeometry.paths[0][measureGeometry.paths[0].length - 1][1];
                const curPos = new Point(curX, curY, map.spatialReference);
                let len = parseInt(result.lengths[0]);
                let textSymbol;
                if(len <= 10000){
                    textSymbol =  new TextSymbol(
                        "長度:" + len + "米"
                    );

                }
                else{
                    textSymbol =  new TextSymbol(
                        "長度:" + len / 1000 + "千米"
                    );
                }
                let textSymbol2 = textSymbol.setColor(new Color([182,0,0])).setFont(
                    new Font("14pt").setWeight(Font.WEIGHT_BOLD)
                ).setHorizontalAlignment("left").setVerticalAlignment('middle');
                graphicsLayer.add(new Graphic(curPos, textSymbol2));
            }

            // 顯示測量面積,周長。
            function outputAreaAndLength(result) {
                const center = measureGeometry.getCentroid();  //獲取查詢區域的中心點
                let area = parseInt(result.areas[0]);
                let len = parseInt(result.lengths[0]);
                let area2, len2;
                if(len > 10000){
                    len2 = len / 1000
                }
                if(area > 1000000){
                    area2 = (area / 1000000).toFixed(3);
                }
                let textSymbol;
                if(area2 && len2){
                    textSymbol =  new TextSymbol(
                        "面積:" + area2 + "平方千米\n" +
                        "周長:" + len2 + "千米"
                    );

                }
                else if(area2){
                    textSymbol =  new TextSymbol(
                        "面積:" + area2 + "平方千米\n" +
                        "周長:" + len + "米"
                    );

                }
                else if(len2){
                    textSymbol =  new TextSymbol(
                        "面積:" + area + "平方米\n" +
                        "周長:" + len2 + "千米"
                    );

                }
                else{
                    textSymbol =  new TextSymbol(
                        "面積:" + area + "平方米\n" +
                        "周長:" + len + "米"
                    );

                }
                let textSymbol2 = textSymbol.setColor(new Color([182,0,0])).setFont(
                    new Font("14pt").setWeight(Font.WEIGHT_BOLD)
                ).setHorizontalAlignment("left").setVerticalAlignment('middle');
                graphicsLayer.add(new Graphic(center, textSymbol2));
            }


            // 新增圖形
            function addGraphic(event) {
                // 使繪圖工具無效。
                // draw.deactivate();
                // 啟用地圖導航.
                map.enableMapNavigation();
                let symbol;
                if ( event.geometry.type === "point" || event.geometry.type === "multipoint") {
                    symbol = markerSymbol;
                    // symbol = textSymbol;
                }
                else if ( event.geometry.type === "line" || event.geometry.type === "polyline") {
                    symbol = lineSymbol;
                }
                else {
                    symbol = fillSymbol;
                }

                const measureOrNot = document.getElementById("measure").checked;
                // 繪製並測量
                if(measureOrNot){
                    // 後邊的程式碼,不能處理extent型別的面積長度測量,
                    // 所以此處需特殊處理。
                    // 即,把extent型別的資料結構,重構成polygon型別的資料結構,即可。
                    if(event.geometry.type == "extent"){
                        let center = event.geometry.getCenter();
                        // console.log('center:', center);
                        const polygonJson = {
                            "rings": [[
                                [event.geometry.xmin, event.geometry.ymax],
                                [event.geometry.xmax, event.geometry.ymax],
                                [event.geometry.xmax, event.geometry.ymin],
                                [event.geometry.xmin, event.geometry.ymin],
                                [event.geometry.xmin, event.geometry.ymax]
                            ]],
                            "spatialReference": {"wkid": 4326},
                            "cache": {
                                "geoShape": "polygon",
                                "_centroid": center,
                                "_extent": {
                                    "xmin": event.geometry.xmin,
                                    "ymin": event.geometry.ymin,
                                    "xmax": event.geometry.xmax,
                                    "ymax": event.geometry.ymax,
                                    "spatialReference": event.geometry.spatialReference
                                },
                                "_partwise": null,

                            }
                        };
                        const polygon = new Polygon(polygonJson);
                        polygon.setCacheValue("geoShape", polygon.type);
                        doMeasure(polygon);
                    }
                    else {
                        event.geometry.setCacheValue("geoShape", event.geometry.type);
                        doMeasure(event.geometry);
                    }
                }
                // 只繪製不測量
                else {
                    event.geometry.setCacheValue("geoShape", event.geometry.type);
                    graphicsLayer.add(new Graphic(event.geometry, symbol));
                }

            }


            //初始化工具欄
            function initToolbar() {
                // 建立繪圖工具。
                // showTooltips,預設為true。
                draw = new Draw(map, { showTooltips: true });
                // 繪圖工具監聽畫完事件。
                // console.log('graphicsLayer:', graphicsLayer);
                draw.on("draw-end", addGraphic);
                // undo_redo_arr,用於儲存 操作撤消恢復事件時 的圖形物件。
                undo_redo_arr = [];
                // clear_arr,用於儲存清除操作,清除的所有圖形物件。
                let clear_arr = [];
                let len_undo_redo, len_clear, len_graphics;
                let textAdd;
                // 繪圖工具監聽 繪製選項。
                on(dom.byId("drawOptions"), "click", function(event){
                    // 沒有事件的元素。
                    if (event.target.id === "drawOptions" ) {
                        return;
                    }
                    const tool = event.target.id.toLowerCase();
                    // 禁用地圖導航。
                    map.disableMapNavigation();
                    // 繪圖工具啟用相應的繪圖功能。
                    draw.activate(tool);
                    if(textAdd){
                        textAdd.remove();
                    }
                });
                // 監聽撤消事件。
                on(dom.byId("undo"), "click", function(event) {
                    // 每單擊一次按鈕,就更新一遍各個陣列的長度。
                    len_clear = clear_arr.length;
                    len_graphics = graphicsLayer.graphics.length;
                    // 撤消 清除事件。
                    if(len_graphics == 0 && len_clear != 0){
                        // 前提:檢視中沒圖形,清除陣列有圖形,才能撤消清除操作。
                        for(let i=0;i<len_clear;i++){
                            graphicsLayer.add(clear_arr.pop());
                        }
                    }
                    // 撤消單個圖形(多點繪製的是多個點,撤消的話一同撤消。)
                    else if(len_graphics != 0){
                        // 前提:檢視中有圖形,才能撤消繪製的單個圖形。
                        let graphic_pop = graphicsLayer.graphics.pop();
                        // remove的兩個引數,第一個是要移除的圖形,第二個是該圖形對應的html元素節點。
                        graphicsLayer.remove(graphic_pop, graphic_pop.getNode());
                        undo_redo_arr.push(graphic_pop);
                        // console.log('undo_redo_arr:', undo_redo_arr);
                    }
                });
                // 監聽恢復事件。
                on(dom.byId("redo"), "click", function (event) {
                    // 每單擊一次恢復按鈕,就更新一遍undo_redo_arr陣列的長度。
                    len_undo_redo = undo_redo_arr.length;
                    // 恢復,是相對於撤消而言的。即,只能恢復撤消的圖形。
                    if(len_undo_redo != 0){
                        // 前提是 undo_redo_arr陣列不為空,才有圖形可恢復。
                        graphicsLayer.add(undo_redo_arr.pop());
                    }
                });
                // 監聽清除事件。
                on(dom.byId("clearAll"), "click", function (event) {
                    // 清除檢視中所有圖形 事件。
                    len_graphics = graphicsLayer.graphics.length;
                    for(let i=0;i<len_graphics;i++){
                        let graphic_pop = graphicsLayer.graphics.pop();
                        clear_arr.push(graphicsLayer.remove(graphic_pop, graphic_pop.getNode()));
                    }
                });
                // 監聽停止標繪事件。
                on(dom.byId("stop"), "click", function (event) {
                    draw.deactivate();
                    if(textAdd){
                        textAdd.remove();
                    }
                });
                // 監聽新增文字事件。
                on(dom.byId("text"), "click", function (event) {
                    // console.log('text');
                    draw.deactivate();
                    textAdd = map.on("click", function(event) {
                        // console.log('event:', event);
                        graphicsLayer.add(
                            new Graphic(event.mapPoint, new TextSymbol("Multi-Line \n Text"), {})
                        );
                    });
                });
            }

            //建立右鍵選單
            function createGraphicsMenu(){
                ctxMenuForGraphics = new Menu({});
                // 在graphicsLayer圖層裡的元素,重疊時,也有上下之分。
                // 即,重疊的元素,哪個在上邊,哪個在下邊。
                //當滑鼠在graphicsLayer圖層的圖形上方時繫結該圖形的點選事件
                let selected;
                graphicsLayer.on("mouse-over", function(event) {
                    selected = event.graphic;
                    // 右鍵選單繫結dom節點。不繫結則“刪除”標籤不彈出。
                    // event.graphic.getDojoShape().getNode(), 指的就是圖形對應的html方式的表達。
                    ctxMenuForGraphics.bindDomNode(event.graphic.getNode());
                });
                //當滑鼠移出graphicsLayer圖層的圖形上方時取消繫結該圖形的點選事件
                graphicsLayer.on("mouse-out", function(event) {
                    ctxMenuForGraphics.unBindDomNode(event.graphic.getNode());
                });
                ctxMenuForGraphics.addChild(new MenuItem({
                    label: "刪除",
                    // 此處的onClick是在“刪除”標籤上的onClick。
                    onClick: function () {
                        undo_redo_arr.push(graphicsLayer.remove(selected));
                    }
                }));
            }

            // 主函式。
            function main() {
                // 建立popup彈出層
                const popup = new Popup(null, domConstruct.create("div"));
                // 地圖
                map = new Map("map", {
                    slider:false,
                    center: [121.47003707885744, 31.24853148977224],
                    zoom: 7,
                    infoWindow: popup, // 資訊視窗,彈窗。
                    extent: new Extent(
                        -122.68,45.53,-122.45,45.60,
                        new SpatialReference({wkid:4326 })
                    ) // 猜測:顯示的區域。
                });
                // 新增地圖圖層
                const mapServiceURL = "https://server.arcgisonline.com/ArcGIS/rest/services/ESRI_StreetMap_World_2D/MapServer";
                map.addLayer(new ArcGISTiledMapServiceLayer(mapServiceURL));
                //初始化比例尺
                const scalebar = new Scalebar({
                    map: map,
                    attachTo: "bottom-left",
                    scalebarUnit: "dual",
                });
                //顯示比例尺
                scalebar.show();
                // 建立圖層
                graphicsLayer = new GraphicsLayer({id: "draw"});
                map.addLayer(graphicsLayer);
                // 給地圖新增事件。
                map.on("load", initToolbar);
                // 建立右鍵選單。
                createGraphicsMenu();
                //啟動右鍵選單
                ctxMenuForGraphics.startup();
                //點選地圖響應
                map.on("click", function(event) {
                    //點選空白處隱藏popup
                    if(event.graphic == undefined){
                        popup.hide();
                    }
                    // mapPoint = event.mapPoint;
                });

                // 搜尋框
                const search = new Search({
                    map: map,
                    graphicsLayer: graphicsLayer,
                }, "search");
                search.startup();

                // 彈出框資訊
                graphicsLayer.on("click",  function(event) {
                    let details;
                    if(event.graphic.geometry.getCacheValue("geoShape") == "polygon"){
                        // const details = '圖形: ' + event.graphic.geometry.cache.geoShape + '<br>';
                        details = '圖形型別: ' + event.graphic.geometry.getCacheValue("geoShape") + '<br>' +
                        '圖形長度:'  + event.graphic.geometry.getCacheValue("長度") + '<br>' +
                        '圖形面積:'  + event.graphic.geometry.getCacheValue("面積");
                    }
                    else if(event.graphic.geometry.getCacheValue("geoShape") == "polyline"){
                        details = '圖形型別: ' + event.graphic.geometry.getCacheValue("geoShape") + '<br>' +
                            '圖形長度:'  + event.graphic.geometry.getCacheValue("長度");
                    }
                    else if(event.graphic.geometry.getCacheValue("geoShape") == "point"){
                        details = '圖形型別: ' + event.graphic.geometry.getCacheValue("geoShape") + '<br>' +
                            '經度:'  + event.graphic.geometry.getCacheValue("經度") + '<br>' +
                            '緯度:'  + event.graphic.geometry.getCacheValue("緯度");
                    }
                    popup.setTitle('圖形資訊');  // 彈窗標題
                    popup.setContent(details);  // 彈窗內容
                    popup.show(event.mapPoint);  // 彈窗展示及其位置
                });

                // textSymbol =  new TextSymbol("Hello World").setColor(
                //     new Color([128,0,0])).setAlign(Font.ALIGN_START).setAngle(45).setFont(
                //     new Font("12pt").setWeight(Font.WEIGHT_BOLD)) ;
                //用來展示點的symbol
                //鏈式呼叫更有效能優勢。
                markerSymbol = new SimpleMarkerSymbol().setSize(10).setColor(
                    new Color("#00FFFF")).setStyle(SimpleMarkerSymbol.STYLE_SQUARE);
                // 用來展示線的symbol
                lineSymbol = new CartographicLineSymbol(
                    CartographicLineSymbol.STYLE_SOLID, // 線的樣式,實線。
                    new Color([255, 0, 0]), 4,  // 顏色 線寬
                    CartographicLineSymbol.CAP_ROUND,  // 兩頭圓角
                    CartographicLineSymbol.JOIN_MITER, // 連線處尖角
                    5 // 最大斜接長度。斜接長度指的是在兩條線交匯處內角和外角之間的距離。
                    // 如果斜接長度超過 miterLimit 的值,邊角會以 lineJoin 的 "bevel" 型別來顯示。
                );
                //用來展示面的symbol
                fillSymbol = new SimpleFillSymbol(
                    SimpleFillSymbol.STYLE_SOLID, // 實心
                    new SimpleLineSymbol(
                        SimpleLineSymbol.STYLE_SOLID, // 實線
                        new Color('#fff'), // 邊/線的顏色
                        1 // 邊的寬度
                    ),
                    new Color([0, 255, 0, 0.2]) // 填充的顏色及透明度
                );
                // console.log('markerSymbol:', markerSymbol);
                // console.log('lineSymbol:', lineSymbol);
                // console.log('fillSymbol:', fillSymbol);
            }

            let map, graphicsLayer, draw, ctxMenuForGraphics;
            let measureGeometry, undo_redo_arr;
            let textSymbol, markerSymbol,lineSymbol,fillSymbol;

            const geometryServiceUrl="http://10.254.11.41:6080/arcgis/rest/services/Utilities/Geometry/GeometryServer";
            esriConfig.defaults.io.proxyUrl = "/proxy/";
            esriConfig.defaults.io.alwaysUseProxy = false;
            const geometryService = new GeometryService(geometryServiceUrl);
            // esriConfig.defaults.geometryService = new GeometryService(geometryServiceUrl);

            main();
        });
    </script>
</head>

<body class="claro" style="font-size: 0.75em;">
    <!--繪圖選單。-->
    <div id="drawOperations">
        <div align="left">繪圖選項</div>
        <div id="drawOptions">
            <img id="Point" src="../images/multipoint.png">
            <img id="Line" src="../images/line.png">
            <img id="Polyline" src="../images/polyline.png">
            <img id="FreehandPolyline" src="../images/freehand_polyline.png">
            <img id="Triangle" src="../images/triangel.png">
            <img id="Extent" src="../images/rectangle.png">
            <img id="Circle" src="../images/circle.png">
            <img id="Ellipse" src="../images/ellipse.png">
            <img id="Polygon" src="../images/polygon.png">
            <img id="FreehandPolygon" src="../images/freehand_ploygon.png">
        </div>
        <div>新增文字</div>
        <img id="text" src="../images/text.png">
        <div><input id="measure" type="checkbox">繪製並測量</div>
        <button id="stop">停止標繪</button>
        <button id="undo">撤消</button>
        <button id="redo">恢復</button>
        <button id="clearAll" >清除</button>
    </div>
    <!--搜尋框。-->
    <div id="search"></div>
    <!--地圖顯示區。-->
    <div id="map" data-dojo-type="dijit/layout/ContentPane" data-dojo-props="region:'center'">
    </div>
</body>

</html>