背景
最近有個採用React的項目用到了ArcGis,發現跟之前的用法非常不同,而且經過我們調研後發現,相關資料並不多見,並且相當分散,主要集中在github和arcgis for developer上。這也算是踩過的一個比較深的坑,在這裡總結一下,希望能幫到大家。 基本原理
要在react中使用arcgis的api可能要比大家想象的要複雜一些,因為arcgis並沒有給大家提供標準的react組件,而是要依賴esri-loader-react,esri-loader等多個組件的聯合使用才可以。
有時間的同志可以看看看這篇文章,拜讀一下大神的文章一切都明白了。
最核心的下面這段話
The ArcGIS API for JavaScript is written in Dojo and distributed as large library of AMD modules. Unfortunately, most module loaders, including webpack, implement the AMD “standard” differently than the way that Dojo does, specifically in the area of plugins. There is a Dojo loader for webpack that tries to address those differences, but as of time of writing it is not capable of loading ArcGIS modules. Even once that issue is resolved, I still think you’ll want to understand the workarounds below so you can decide which is the best solution for your application.
簡單的說就是arcgisapi的JavaScript是基於Dojo實現的,並且需要通過AMD模組匯入。但是呢,webpack的AMD實現方式與Dojo不同,因此產生了衝突,因此我們需要採用各種奇怪的方法來曲線救國解決這兩者的衝突。
在本文中我們採用了建立專用的地圖載入模組的方案,即通過esriLoader的dojoRequire來引入arcgis的類。
範例程式碼如下所示
//匯入esri-loaderimport * as esriLoader from 'esri-loader';// 使用dojoRequire來一如esri/map類esriLoader.dojoRequire(['esri/map'], (Map) => { // 使用Map類建立地圖 let map = new Map('mapNode', { center: [-118, 34.5], zoom: 8, basemap: 'dark-gray' });});
實際代碼
來跟著我大喊一句,show me the code。
封裝控制項
import React from 'react';import { dojoRequire } from 'esri-loader'import EsriLoader from 'esri-loader-react'export default class Map extends React.Component { //採用建構函式的方法解決不用頁面同時調用地圖組件產生id衝突問題 constructor(props) { super(props); let timeStample = new Date().getTime(); this.state = { mapDivId: "mapdiv" + timeStample, mainMap: {} }; } render() { const mapOptions = { url: 'http://ip:8080/arcgis_js_api/library/3.9/3.9/init.js' } let flag = this.props.getShowType(); let mapStyle; if (flag !== "index") { //使用自適應大小 mapStyle = { height: '400px', width: '430px' }; } else { mapStyle = { height: '100vh', width: '70vw' }; } return ( <div> <EsriLoader options={mapOptions} ready={() => console.timeEnd('JSAPI loaded')} /> <div id={this.state.mapDivId} style={mapStyle}> </div> </div> ) } componentDidMount() { dojoRequire( ['esri/layers/WebTiledLayer', 'esri/map'], (WebTiledLayer, EsriMap,InfoTemplate) => { this.state.mainMap = new EsriMap(this.state.mapDivId, { autoResize: true, zoom: 5, maxZoom: 15, center: [107.5, 27] }); let thisMap=this; this.state.mainMap.on("load", function(){ thisMap.state.mainMap.graphics.enableMouseEvents(); }); let bgMapUrl = 'http://ip:8080'; bgMapUrl = bgMapUrl + '/WebMapTileServer/map/WebMap/Tiles?x=${col}&y=${row}&z=${level}'; //瓦片底圖 let baseMap = new WebTiledLayer(bgMapUrl); this.state.mainMap.addLayer(baseMap);//google地圖 this.setExtent({ minLat: "23", maxLat: "31", maxLon: "111.5", minLon: "103.5" }); } ); } addEventCallBack2Layer(layer,event, cb) { dojo.connect(layer,event, cb); } showInfoTemplate(info){ //彈出infoWindow this.state.mainMap.infoWindow.show(); } setExtent(scope) { // scope={"minLon":"0","maxLon":"10","minLat":"0","maxLat":"10"}; console.info("setExtent"); dojoRequire(["esri/geometry/Extent"], (Extent) => { let extent = new Extent({ "xmin": parseFloat(scope.minLon), "ymin": parseFloat(scope.minLat), "xmax": parseFloat(scope.maxLon), "ymax": parseFloat(scope.maxLat), "spatialReference": { "wkid": 4326 } }); this.state.mainMap.setExtent(extent); }); } //增加圖層; addLayer(layerName) { dojoRequire( ['esri/layers/GraphicsLayer'], (GraphicsLayer) => { let layer; layer = new GraphicsLayer(); layer.id = layerName; this.state.mainMap.addLayer(layer); return layer; }); } // 清除一個圖層; clearLayer(layerName) { let layer = this.state.mainMap.getLayer(layerName); if (layer !== null) layer.clear(); } getLayer(layerName) { return this.state.mainMap.getLayer(layerName); } //清除所有圖層; clearAllLayers() { var arr = mainMap.graphicsLayerIds; arr.forEach((layer) => { this.state.mainMap.clearLayer(layer); }); } ////增加表徵圖marker addMarker(data) { // 添加marker到地圖中,例如人員、裝置、任務 let locatedPoint = { "geometry": { "x": data.marker.lng,//經度 "y": data.marker.lat,//緯度 "spatialReference": { "wkid": 4326 }, }, "symbol": { "url": "/style/images/" + data.marker.type + ".png", "height": data.marker.height, "width": data.marker.width, "angle": data.marker.angle, "type": "esriPMS" } }; // 檢測是否設定infowindow資訊 if (data.infoTemplate !== null) { locatedPoint.geometry.infoTemplate = data.infoTemplate; } //建立地圖資訊 dojoRequire( ['esri/graphic'], (Graphic) => { let gra = new Graphic(locatedPoint); if( data.marker.attrs===undefined) data.marker.attrs={}; // 設定屬性 if( data.marker.attrs===undefined) data.marker.attrs={}; data.marker.attrs.graphic_id = data.marker.id; gra.setAttributes(data.marker.attrs); // 檢測是否有當前marker所需添加到的圖層,如果沒有建立圖層 let layer = this.state.mainMap.getLayer(data.layer); if (layer === null) { layer = this.state.mainMap.addLayer(data.layer); } return layer.add(gra); console.log("bigon"); }); } closeWindowInfo(evt){ this.state.mainMap.infoWindow.hide(); } showWindowInfo(evt){ this.state.mainMap.infoWindow.setContent(evt.graphic.geometry.infoTemplate.content); this.state.mainMap.infoWindow.show(evt.screenPoint,this.state.mainMap.getInfoWindowAnchor(evt.screenPoint)); } // 繪製軌跡 addPath(pathParameters) { // 首先擷取圖層, 如果不存在則建立新圖層 let pathLayer = this.state.mainMap.getLayer(pathParameters.layer); // 建立路徑 let lineSymbol = {}; let s=pathParameters.from; let e=pathParameters.to; let geo=[[s.x,s.y],[e.x,e.y]]; lineSymbol.geometry = { "paths":[geo], "spatialReference": { "wkid": 4326 } }; lineSymbol.symbol = { "color": [255, 0, 0, 255], "width": 5, "type": "esriSLS", "style": "esriSLSSolid" }; dojoRequire( ['esri/graphic'], (Graphic) => { // 添加路徑到圖層 pathLayer.add(new Graphic(lineSymbol)); }); }}
package.json
{ "name": "ZEWDemo", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "webpack", "start": "webpack-dev-server --host 0.0.0.0 --port 9001 --colors --hot --inline --content-base ./build" }, "author": "", "license": "ISC", "devDependencies": { "babel-core": "^6.26.0", "babel-loader": "^7.1.2", "babel-preset-es2015": "^6.24.1", "babel-preset-react": "^6.24.1", "css-loader": "^0.28.7", "esri-loader-react": "^0.2.2", "file-loader": "^0.11.1", "jsx-loader": "^0.13.2", "less": "^2.7.2", "less-loader": "^4.0.5", "react": "^15.6.1", "react-dom": "^15.6.1", "require": "2.4.20", "style-loader": "^0.18.2", "url-loader": "^0.5.8", "webpack": "^3.5.5", "webpack-dev-server": "^2.7.1" }, "dependencies": { "bootstrap": "^3.3.7", "echarts-for-react": "^1.4.4", "esri-loader": "^0.3.0", "esri-loader-react": "^0.2.2", "mqtt": "^2.11.0", "rc-tree-select": "^1.10.7", "react": "^15.6.1", "react-bootstrap": "^0.31.2", "react-bootstrap-date-time-picker": "0.0.3", "react-bootstrap-table": "^4.0.2", "react-date-range": "^0.9.4", "react-datetime": "^2.10.1", "react-dom": "^15.6.1", "react-echarts": "^0.1.1", "react-ui-tree": "^3.0.0" }}
參考資料
1.http://tomwayson.com/2016/11/27/using-the-arcgis-api-for-javascript-in-applications-built-with-webpack/
2.https://github.com/Esri/esri-loader
3.https://developers.arcgis.com/