在一些地图地图应用中,距离、面积测量属于基础功能。ArcGIS API for JavaScript有单独提供一个测量的微件,就像鹰眼、比例尺那样拿来就可以用,但是具体效果不是我想要的。之前在项目中有测量这方面的需求,在网上直接找了代码就粘上去了,后来测试的时候发现不能用,经过对比官方API文档,发现其对坐标系还有些限制。因此,根据ArcGIS提供的接口进一步封装了一个测量类,里面提供两种测量方式,一种是采用几何服务(GeometryServer)
,适用于具备ArcGIS Server环境的项目;另一种是GeometryEngine
类,适用于坐标系为**[4326,3857,102100,任意平面坐标系]**之一的项目,这个主要是那些不使用ArcGIS平台,但采用ArcGIS JS API的项目用到。个人更加推荐第一种,不需要考虑坐标系问题,只要有个几何服务(装ArcGIS Server的时候就已经带着了,或者可以使用ArcGIS官方的一个地址,就是有点慢)地址就可以了。
几何服务
地址时,采用GeometryEngine
测量;Draw
工具类,距离测量时需要用到地图单击
监听事件,通过记录单击点的坐标计算距离,面积测量双击结束触发draw-complete
监听事件,记录绘制的多边形(geometry)计算面积;Draw
工具;Measure.js
define([
"dojo/_base/lang",
"dojo/_base/declare",
"dojo/number",
"esri/layers/GraphicsLayer",
"esri/toolbars/draw",
"esri/symbols/TextSymbol",
"esri/symbols/SimpleMarkerSymbol",
"esri/symbols/SimpleLineSymbol",
"esri/symbols/SimpleFillSymbol",
"esri/symbols/Font",
"esri/Color",
"esri/graphic",
"esri/geometry/Polyline",
"esri/geometry/Point",
"esri/geometry/Polygon",
"esri/geometry/geometryEngine",
"esri/tasks/AreasAndLengthsParameters",
"esri/tasks/LengthsParameters",
"esri/tasks/GeometryService",
], function (
lang,
declare,
number,
GraphicsLayer,
Draw,
TextSymbol,
SimpleMarkerSymbol,
SimpleLineSymbol,
SimpleFillSymbol,
Font,
Color,
Graphic,
Polyline,
Point,
Polygon,
geometryEngine,
AreasAndLengthsParameters,
LengthsParameters,
GeometryService,
) {
return declare(null, {
_map: null,
_measureMethod: "GeometryServer",//默认测量方式为几何服务
_distanceMeasure: false,//距离测量flag
_areaMeasure: false,//面积测量
_options: {
map: this._map,
geometryServiceUrl: ''
},
_mapClickListener: null,
_inputPoints: [],
_totalDistance: 0.00,
_totalGraphic: null,
constructor: function (options) {
lang.mixin(this._options, options);
this._checkParams(options);
this._map = this._options.map;
this._geometryServiceUrl = this._options.geometryServiceUrl;
//监听地图单击事件
this._mapClickListener = this._map.on('click', this._mapClickHandler.bind(this));
this._drawBar = new Draw(this._map);
this._drawBar.on('draw-complete', this._drawEnd.bind(this))
this._map.addLayer(new GraphicsLayer({
id: "measureLayer"
}));
this._font = new Font('12px').setWeight(Font.WEIGHT_BOLD);
this._defaultMarkerSymbol = new SimpleMarkerSymbol(SimpleMarkerSymbol.STYLE_CIRCLE, 7,
new SimpleLineSymbol(SimpleLineSymbol.STYLE_SOLID,
new Color([255, 0, 0]), 1), new Color([255, 0, 0]));
},
//测量距离
measureDistance: function () {
this.clearMeasureActionAndGraphics();
this._distanceMeasure = true;//激活距离测量
this._totalDistance = 0.00;//总长度重置为0
this._inputPoints = [];//输入点数组置为空
this._drawBar.activate(Draw.POLYLINE);
},
//地图单击事件处理
_mapClickHandler: function (evt) {
if (this._distanceMeasure) {
this._distanceMeasureHandler(evt.mapPoint);
}
},
/**
* 距离测量处理
* @param mapPoint 单击的点
*/
_distanceMeasureHandler: function (mapPoint) {
let me = this;
this._inputPoints.push(mapPoint);//地图上添加鼠标点击处的点
//添加起点
let textSymbol;
if (this._inputPoints.length === 1) {
//记录第一个点
textSymbol = new TextSymbol("起点", this._font, new Color([204, 102, 51]));
textSymbol.setOffset(0, -20);
this._map.getLayer("measureLayer").add(new Graphic(mapPoint, textSymbol));
}
//鼠标点击处添加点,并设置其样式
this._map.getLayer("measureLayer").add(new Graphic(mapPoint, this._defaultMarkerSymbol));
if (this._inputPoints.length >= 2) {
if (this._measureMethod === "GeometryServer") {
//方式一:利用ArcGIS Server的GeometryService服务,适用于具备ArcGIS Server环境的项目
let lengthParams = new LengthsParameters();
let url = this._geometryServiceUrl;
let geometryService = new GeometryService(url);
lengthParams.distanceUnit = GeometryService.UNIT_METER;
lengthParams.calculationType = "preserveShape";
let p1 = this._inputPoints[this._inputPoints.length - 2];
let p2 = this._inputPoints[this._inputPoints.length - 1];
//同一个点,解决重复添加的bug
if (p1.x == p2.x && p1.y == p2.y)
return;
//在两点之间画线将两点连接起来
let polyline = new Polyline(this._map.spatialReference);
polyline.addPath([p1, p2]);
lengthParams.polylines = [polyline];
//根据参数,动态计算长度
geometryService.lengths(lengthParams, function (distance) {
let _distance = number.format(distance.lengths[0]);//长度单位改为米
// debugger;
_distance = _distance.replace(",", '');//返回值每3位','隔开
me._totalDistance += parseFloat(_distance);//计算总长度
let beetwentDistances = _distance + "米";
let tdistance = new TextSymbol(beetwentDistances, me._font, new Color([204, 102, 51]));
tdistance.setOffset(40, -3);
me._map.getLayer("measureLayer").add(new Graphic(p2, tdistance));
if (me._totalGraphic) //如果总长度存在的话,就删除总长度Graphic
me._map.getLayer("measureLayer").remove(me._totalGraphic);
let total = number.format(me._totalDistance, {
pattern: "#.000" });//保留三位小数
//设置总长度显示样式,并添加到地图上
let totalSymbol = new TextSymbol("总长度:" + total + "米", me._font, new Color([204, 102, 51]));
totalSymbol.setOffset(40, -20);
me._totalGraphic = me._map.getLayer("measureLayer").add(new Graphic(p2, totalSymbol));
});
} else {
//方式二:利用ArcGIS API中自带的GeometryEngine类,适用于地图坐标系(wkid)为3857或4326或平面投影坐标系
//设置距离测量的参数
let p1 = this._inputPoints[this._inputPoints.length - 2];
let p2 = this._inputPoints[this._inputPoints.length - 1];
//同一个点,解决重复添加的bug
if (p1.x == p2.x && p1.y == p2.y)
return;
//在两点之间画线将两点连接起来
let polyline = new Polyline(this._map.spatialReference);
polyline.addPath([p1, p2]);
let distance = 0;
//根据参数,动态计算长度
if (this._map.spatialReference.wkid == "3857" || (this._map.spatialReference.wkid == "102100") || this._map.spatialReference.wkid == "4326") {
//在web麦卡托投影和WGS84坐标系下的计算方法
distance = geometryEngine.geodesicLength(polyline, "meters");//geodesicArea适用坐标系见官网API
} else {
//在其他投影坐标系下的计算方法
distance = geometryEngine.planarLength(polyline, "meters");//planarArea适用于平面投影坐标系
}
let _distance = number.format(distance / 1000);
this._totalDistance += parseFloat(_distance);//计算总长度
let beetwentDistances = _distance + "千米";
let tdistance = new TextSymbol(beetwentDistances, this._font, new Color([204, 102, 51]));
tdistance.setOffset(40, -3);
this._map.getLayer("measureLayer").add(new Graphic(p2, tdistance));
if (this._totalGraphic) //如果总长度存在的话,就删除总长度Graphic
this._map.getLayer("measureLayer").remove(this._totalGraphic);
let total = number.format(this._totalDistance, {
pattern: "#.000" });//保留三位小数
//设置总长度显示样式,并添加到地图上
let totalSymbol = new TextSymbol("总长度:" + total + "千米", this._font, new Color([204, 102, 51]));
totalSymbol.setOffset(40, -20);
this._totalGraphic = this._map.getLayer("measureLayer").add(Graphic(p2, totalSymbol));
}
}
},
/**
* 面积测量,对外暴露
*/
measureArea: function () {
this.clearMeasureActionAndGraphics();
this._areaMeasure = true;
this._drawBar.activate(Draw.POLYGON);
},
/**
* 绘制结束监听事件
*/
_drawEnd: function (evt) {
if (this._distanceMeasure) {
this._inputPoints = [];
}
if (this._areaMeasure) {
let me = this;
let geometry = evt.geometry;
if (this._measureMethod === "GeometryServer") {
//方式一:利用ArcGIS Server的GeometryService服务,适用于具备ArcGIS Server环境的项目
let areasAndLengthParams = new AreasAndLengthsParameters();
let url = this._geometryServiceUrl;
let geometryService = new GeometryService(url);
areasAndLengthParams.lengthUnit = GeometryService.UNIT_METER;
areasAndLengthParams.areaUnit = GeometryService.UNIT_SQUARE_METERS;//单位改为平方米
areasAndLengthParams.calculationType = 'preserveShape';
geometryService.simplify([geometry], function (simplifiedGeometries) {
areasAndLengthParams.polygons = simplifiedGeometries;
geometryService.areasAndLengths(areasAndLengthParams, function (result) {
let font = new Font("16px", Font.STYLE_NORMAL, Font.VARIANT_NORMAL, Font.WEIGHT_BOLDER);
let lengthsResult = new TextSymbol(number.format(result.areas[0], {
pattern: '#.000'
}) + "平方米", font, new Color([204, 102, 51]));
let spoint = new Point(evt.geometry.getExtent().getCenter().x, evt.geometry.getExtent().getCenter().y, me._map.spatialReference);
me._map.getLayer("measureLayer").add(new Graphic(spoint, lengthsResult)); //在地图上显示测量的面积
});
});
} else {
//方式二:利用ArcGIS API中自带的GeometryEngine类,适用于地图坐标系(wkid)为3857或4326或平面投影坐标系
let area = 0;
if ((geometry.spatialReference.wkid == "4326") || (geometry.spatialReference.wkid == "3857") || (geometry.spatialReference.wkid == "102100")) {
area = geometryEngine.geodesicArea(evt.geometry, "square-kilometers");//geodesicArea适用坐标系见官网API
} else {
area = geometryEngine.planarArea(evt.geometry, "square-kilometers");//planarArea适用于平面投影坐标系
}
this.drawArea+=parseFloat(area.toFixed(3));
let font = new Font("18px", Font.STYLE_NORMAL, Font.VARIANT_NORMAL,
Font.WEIGHT_BOLDER);
let areaResult = new TextSymbol(number.format(area, {
pattern: '#.000' }) +
"平方千米", font, new Color([204, 102, 51]));
let spoint = new Point(geometry.getExtent().getCenter().x, geometry.getExtent().getCenter().y, this._map.spatialReference);
this._map.getLayer("measureLayer").add(new Graphic(spoint, areaResult));//在地图上显示测量的面积
}
}
let resultSymbol;
switch (evt.geometry.type) {
case 'polyline':
resultSymbol = new SimpleLineSymbol(SimpleLineSymbol.STYLE_SOLID, new Color([255,0,0,0.8]), 2);
break;
case 'polygon':
resultSymbol = new SimpleFillSymbol(SimpleFillSymbol.STYLE_SOLID, new SimpleLineSymbol(SimpleLineSymbol.STYLE_SOLID, new Color([255, 0, 0]), 2), new Color([255, 255, 0, 0.25]));
break;
}
this._map.getLayer("measureLayer").add(new Graphic(evt.geometry, resultSymbol));
this._distanceMeasure = false;
this._areaMeasure = false;
this._drawBar.deactivate();
},
/**
* 清除测量动作及图上图形
*/
clearMeasureActionAndGraphics: function () {
this._distanceMeasure = false;
this._areaMeasure = false;
this._map.getLayer("measureLayer").clear();
this._drawBar.deactivate();
},
/**
* 参数校验
*/
_checkParams: function (options) {
if (!options.map) {
throw new Error("参数中必须包含map对象,参数格式:{'map': map}");
}
if (!options.geometryServiceUrl) {
this._measureMethod = "GeometryEngine";//未传入几何服务地址,改用GeometryEngine类进行测量
console.warn("未传入参数'geometryServiceUrl',采用方式2计算,若地图坐标系非['4326','3857','任意平面投影坐标系']之一,可能测量失败");
}
},
})
});
下面通过一个单页面测试一下封装的测量类。
index.html
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>基于ArcGIS JS API实现的两种距离和面积测量方式title>
<link rel="stylesheet" href="https://js.arcgis.com/3.27/esri/css/esri.css">
head>
<style>
html,
body,
#map {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
}
.horizontal-toolbar{
position: absolute;
top: 30px;
right: 75px;
/* width: 100px; */
height: 50px;
overflow: hidden;
background-color: white;
border-radius: 4px;
z-index: 100;
}
.horizontal-toolbar>li{
position: relative;
margin: 5px;
list-style: none;
float: left;
width: 46px;
height: 46px;
}
style>
<script>
var package_path = window.location.pathname.substring(0, window.location.pathname.lastIndexOf('/'));
//var package_path = window.location.pathname.replace(/\/[^\/]*$/,"");
var dojoConfig = {
// The locationPath logic below may look confusing but all its doing is
// enabling us to load the api from a CDN and load local modules from the correct location.
packages: [{
name: "modules",
location: package_path + '/modules'
}]
};
script>
<script src="https://js.arcgis.com/3.27/">script>
<script>
var map,measureTool;
require([
"esri/map",
"modules/Measure",
"dojo/domReady!"
], function (Map, Measure) {
map = new Map("map", {
basemap: 'topo',
center: [117.02156, 36.65993],
zoom: 16
});
//方式一:使用几何服务
var options = {
map: map,
geometryServiceUrl: 'https://utility.arcgisonline.com/ArcGIS/rest/services/Geometry/GeometryServer'
};
//方式二:不传几何服务参数,使用GeometryEngine
// var options = {
// map: map
// };
measureTool = new Measure(options);
});
function measureDistance () {
measureTool.measureDistance();
}
function measureArea () {
measureTool.measureArea();
}
function clearMeasureActionAndGraphics () {
measureTool.clearMeasureActionAndGraphics();
}
script>
<body>
<div id="map" style="position:relative;">
<ul class="horizontal-toolbar">
<li onclick="measureDistance()">
<img src="./icons/distance.png" alt="测量距离" title="测量距离">
li>
<li onclick="measureArea()">
<img src="./icons/area.png" alt="测量面积" title="测量面积">
li>
<li onclick="clearMeasureActionAndGraphics()">
<img src="./icons/clear.png" alt="清除" title="清除">
li>
ul>
div>
body>
html>
calculationType
,包括三种:planar(平面)
,geodesic(曲面)
,preserveShape(通用)
,面积测量类似;GeometryEngine
测量时注意区分geodesicArea
,planarArea
,前者适用于wkid为[4326、3857、102100],后者适用于平面坐标系,如果地图坐标系与使用的测量方法匹配不起来可能会出现错误;Calculates the area of the input geometry. As opposed to planarArea(), geodesicArea takes into account the curvature of the earth when performing this calculation. Therefore, when using input geometries with a spatial reference of either WGS-84 (wkid: 4326) or Web Mercator Auxiliary Sphere (wkid: 3857), it is best practice to calculate areas using geodesicArea(). If the input geometries have a projected coordinate system other than Web Mercator, use planarArea() instead.
This method only works with WGS-84 (wkid: 4326) and Web Mercator (wkid: 3857) spatial references.
3857
和102100
表示的是同一个投影坐标系,只是wkid
有所不同,3857
是较新版本的(latestWkid
);3857 | WGS_1984_Web_Mercator_Auxiliary_Sphere |
---|
PROJCS[“WGS_1984_Web_Mercator_Auxiliary_Sphere”,GEOGCS[“GCS_WGS_1984”,DATUM[“D_WGS_1984”,SPHEROID[“WGS_1984”,6378137.0,298.257223563]],PRIMEM[“Greenwich”,0.0],UNIT[“Degree”,0.0174532925199433]],PROJECTION[“Mercator_Auxiliary_Sphere”],PARAMETER[“False_Easting”,0.0],PARAMETER[“False_Northing”,0.0],PARAMETER[“Central_Meridian”,0.0],PARAMETER[“Standard_Parallel_1”,0.0],PARAMETER[“Auxiliary_Sphere_Type”,0.0],UNIT[“Meter”,1.0]]
102100 | WGS_1984_Web_Mercator_Auxiliary_Sphere |
---|
PROJCS[“WGS_1984_Web_Mercator_Auxiliary_Sphere”,GEOGCS[“GCS_WGS_1984”,DATUM[“D_WGS_1984”,SPHEROID[“WGS_1984”,6378137.0,298.257223563]],PRIMEM[“Greenwich”,0.0],UNIT[“Degree”,0.0174532925199433]],PROJECTION[“Mercator_Auxiliary_Sphere”],PARAMETER[“False_Easting”,0.0],PARAMETER[“False_Northing”,0.0],PARAMETER[“Central_Meridian”,0.0],PARAMETER[“Standard_Parallel_1”,0.0],PARAMETER[“Auxiliary_Sphere_Type”,0.0],UNIT[“Meter”,1.0]]
源文件下载地址:https://download.csdn.net/download/wml00000/11114680