目录
前言
无论是规划业务、土地业务,还是工业部门、环保部门,这些业主都维护了许多的地块类数据,比如控规数据、土规数据、用地类型数据、地貌类型数据等等。面对这些数据,业主往往有按空间范围对地类等属性进行面积统计的需求。本文我们将从设计与实现两个角度系统地探讨这个功能。先提供效果演示:
查询统计功能效果图
需求梳理
地理信息相关的系统需求设计一般需要由掌握丰富的GIS理论知识的人员来完成,从已有知识储备库中分娩地理信息系统解决方案还是纯粹研判需求的可行性来说,一般都如此。
1、确定统计范围
对于范围输入,我们一般采用手动绘制、文件导入以及内置常用区域三种方式,三驾马车可以满足绝大多数用户的需求。一般来说,手动绘制只需要提供多边形绘制能力;文件导入支持的文件类型通常包括shapefile、GeoJSON、CAD文件;常用区域通常是一个常用区域的列表,如各个重点的开发区。
注:shp文件只是shapefile中包含的文件之一,仅仅有shp文件无法将一个空间图层完整表达出来,我们需要一组文件,这组文件我们称为shapefile。
2、统计图层对象选取
WebGIS风格的系统一般都有一个资源目录树。我们在要素统计模块不妨也放一个资源目录树,有了资源目录树,我们就有了资源池,统计对象可以从中轻松选取。
然而,众所周知,ArcGIS系列产品发步的地图服务是按mxd来组织的,而非单个要素图层;基于ArcGIS技术栈搭建的WebGIS网站中,注册于资源目录树中的大多是以MapServer结尾的地图服务,而要素统计的对象明显是单个要素图层,因此我们还需要从中将要素图层提取出来。
3、选取统计字段
针对空间范围的要素统计功能关注的关键除了上述两个方面,还有统计的内容,也就是我们要统计什么。我们先看看潜在的比较基础的场景:
1)统计南港经济区(内置范围)内控规(统计对象)尺度下每种用地类型(统计字段)的要素所占的面积的总和。
2)统计西青区范围内某河流域(上传范围)内水田和水浇地(统计字段)的耕地(统计对象)各占多大面积。
这两处场景对应的结果可能是这样的:
结果一:
地类名称 | 面积(公顷) |
公共管理与公共服务设施用地 | 0.84 |
商业服务业设施用地 | 3.20 |
工业用地 | 748.31 |
物流仓储用地 | 201.43 |
...... | ... |
结果二:
地类名称 | 面积(公顷) |
水浇地 | 894.65 |
水田 | 398.2 |
比较基础的模式可以总结为:统计A图层在B范围内,对于不同的C字段每个要素的D字段值的总和。(本文只总结针对此基础模式的情形的解决方案与技术路线。)
原型设计
1、制作原型
拿出我们的axure,简单拉个原型。
2、功能逻辑
2.1 确定统计空间范围
此处使用三个radio button,分别是导入、绘制以及常用区域,下方区域依据radio button的值显示具体内容。其中,空间范围被导入、绘制或者选择后,随即渲染在地图上。空间范围对目标图层进行裁切,因此统计的累加值通常只能是面积。
2.2 选择统计对象
我们提供两种统计对象的选择方式,内置配置 + 自由选取。自由选取模式下,用户分别自由选择要统计的图层、分组字段以及累加字段。其中分组字段和累加字段是从图层信息中读取的,累加字段要求数值型字段,ArcGIS Server提供的rest服务中包含了这些信息。用户点击“选择其他图层”按钮后,页面中间弹出dialog,其中渲染资源目录树,用户可以通过选择其中的叶子节点来选取地图服务,系统自动识别用户选取的地图服务中包含的要素图层的数量,其中包含多个要素图层的话系统弹出列表供用户进行单选。资源目录树可以如下图所示。
2.3 添加表达式
用户可以在“表达式”输入框内输入条件语句,也可以通过构造器创建,提供表达式功能是为了允许用户在统计之前先做一遍属性过滤。勾选标题右侧的check box表明希望条件生效。表达式构造器的结构可以如下图所示:
2.4 运算与展示
点击“计算”按钮,结果运算完毕后悬浮在中间的容器内。运算结果的最后一列提供高亮和导出按钮,支持当前结果空间数据的高亮或ban文件导出。
关键部分代码
1、提取要素类
从地图服务(MapImageLayer)中提取所有子图层,亦即要素类
const _layer = await layer.when()
const featureLayers = _layer.allSublayers
2、计算统计
这部分是功能的核心实现代码,我们使用用户输入的统计范围边界对目标要素图层进行裁剪,得到范围内所有要素及其属性,然后对其进行分组统计。
async executeStatisticsGeneral(featureLayer, params) {
let {
geometryInput,
expression,
groupField
} = {
...params
}
const geometry = await PolygonFromParams(geometryInput)
const polyline = await getPolylineFromPolygon(geometry)
const area = await getGeodesicAreaOfPolygon(geometry)
// 1、面包含的要素
const result1 = await featureLayer.queryFeatures(new Query({
geometry: geometry,
outFields: '*',
spatialRelationship: 'contains',
returnGeometry: true,
where: expression
}))
// 2、和线相交的要素
const result2 = await featureLayer.queryFeatures(new Query({
geometry: polyline,
outFields: '*',
spatialRelationship: 'intersects',
returnGeometry: true,
where: expression
}))
// 3、线相交的要素与原始范围做相交运算 得出裁出来的要素
const result3 = await this.refreshAreaOfFeaturesByGeometryIntersect(result2 ? result2.features : null, geometry)
// 4、3和1合并成为最后结果
const resultAllTogether = result1.features.concat(result3)
for (let i = 0; i < resultAllTogether.length; i++) {
const element = resultAllTogether[i];
element.area = await getGeodesicAreaOfPolygon(element.geometry)
element[groupField] = element.attributes[groupField]
// geometries.push(element.geometry)
}
let groupResult = lodash.groupBy(resultAllTogether, groupField)
let result = {
area: area,
items: []
}
for (const key in groupResult) {
let totalArea = 0
let unionedGeometry = null
if (Object.hasOwnProperty.call(groupResult, key)) {
const element = groupResult[key];
let _geometries = []
element.forEach(feature => {
totalArea += feature.area
_geometries.push(feature.geometry)
});
unionedGeometry = await unionOfGeometryEngine(_geometries)
}
result.items.push({
name: key,
area: totalArea.toFixed(2),
unionedGeometry: unionedGeometry
})
totalArea = 0
}
return result
}
其中refreshAreaOfFeaturesByGeometryIntersect函数的代码
async refreshAreaOfFeaturesByGeometryIntersect(features, geometry) {
if (!features || features.length < 1 || !geometry) {
return []
}
let geometries = []
let attributes = []
let resultFeatures = []
for (let i = 0; i < features.length; i++) {
const feature = features[i];
geometries.push(feature.geometry)
attributes.push(feature.attributes)
}
let resultGeometries = await intersectOfGeometryEngine(geometries, geometry)
for (let i = 0; i < resultGeometries.length; i++) {
const resultGeometry = resultGeometries[i];
if (resultGeometry) {
resultFeatures.push({
geometry: resultGeometry,
attributes: attributes[i]
})
}
}
return resultFeatures
}
可以观察到,我的代码中并不是简单的面-面相交运算,因为裁剪运算耗时较多,我们通过分段计算的方式,尽量减少裁剪运算的次数,以缩短计算时间。
3、结果可视化
我们将计算结果填入表格,并添加如高亮、导出等操作入口。
<div class="table">
<el-button class="closePanelBtn" type="primary" @click="closeExpand">关闭</el-button>
<div class="area-box flexC">
总面积:<span class="red-text">{{ tableContents.area.toFixed(2) }}</span
>(平方米)
<div class="export" @click="exportExcel">
<i
class="el-icon-download"
style="color: #43c7fe; font-size: 20px; cursor: pointer"
></i>
</div>
</div>
<el-table
:data="tableContents.items"
style="width: 100%"
:height="343"
>
<el-table-column prop="name" :label=tableContents.fieldName align="center" width="195">
</el-table-column>
<el-table-column prop="area" align="center" label="面积" >
</el-table-column>
<el-table-column
width="120"
label="操作"
align="center"
>
<template slot-scope="scope">
<el-button @click="viewKongguiByType(scope.row)" type="text" size="small">查看</el-button>
<el-button @click="exportKongguiByType(scope.row)" type="text" size="small">导出</el-button>
</template>
</el-table-column>
</el-table>
</div>
总结
本文对要素统计的需求提出了一套解决方案,结果显示,本方案应对本类需求通用、可靠。本文给出了该方案的总体思路、具体思路以及关键部分的具体代码。如果对本文任何地方有所疑问或者对更多详细代码感兴趣,欢迎在评论区以及私信探讨!感谢阅读!