等值线/面生成一站式封装
选择要生成的点
- 前台页面
<a-modal
title="设置"
:width="900"
:visible="AnalysisVisible"
@ok="AnalysishandleSubmit"
@cancel="AnalysishandleCancel"
>
<div style="margin-top: 20px">
<span style="width: 120px; display: inline-block; text-align: right; margin-right: 10px">年份:</span>
<el-select v-model="Analysis.year" placeholder="请选择">
<el-option v-for="item in AnalysisYear" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
</div>
<div style="margin-top: 20px">
<span style="width: 120px; display: inline-block; text-align: right; margin-right: 10px">月份:</span>
<el-select v-model="Analysis.month" placeholder="请选择">
<el-option v-for="item in AnalysisMonth" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
</div>
<div style="margin-top: 20px">
<span style="width: 120px; display: inline-block; text-align: right; margin-right: 10px">等值面颜色设定:</span
><color-select :color-list="colorList" v-model="settings.color"></color-select>
</div>
<div style="margin-top: 20px">
<span style="width: 120px; display: inline-block; text-align: right; margin-right: 10px">间隔设定:</span>
<el-select v-model="settings.interval" placeholder="请选择">
<el-option v-for="item in intervaloptions" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
</div>
<div style="margin-top: 20px">
<span style="width: 120px; display: inline-block; text-align: right; margin-right: 10px">生成类型:</span>
<el-button-group>
<el-button :type="type1" @click="typeSele('isobands')">等值面</el-button>
<el-button :type="type2" @click="typeSele('isolines')">等值线 </el-button>
</el-button-group>
</div>
</a-modal>
- 给后台一个数组,返回的数据格式options如下
{
color: "rgba(218,165,32,1)",
extent: (4)[115.63528641258763, 38.69892119142736, 116.3275678594995, 39.17138063219472],
interval: 1,
type: "isobands",
x: (17)[116.13, 116.09, 116.17],
y: (17)[39.14, 39.09, 39.02],
z: (17)[24.6, 24.5, 24.3],
}
传递给mapgis插件(自封装),通过openlayers代码生成等值线
- 封装类
/**
功能: 等值线 等值面
参数:
作者:Moke
邮箱:zjmoke1026@163.com
时间:2021年07月23日 14:22:01
版本:v1.0
修改记录:
修改内容:
修改时间:2021年07月23日 14:22:01
*/
var self;
import geojsonObject from '../../../../public/loading/xa2000.json'
export default class EquivaTool {
constructor(map) {
self = this
this.map = map
this.WFSVectorSource = null
this.WFSVectorLayer = null
this.vectorLayer = null
this.vectorSource = null
this.select = new ol.interaction.Select()
this.dragBox = new ol.interaction.DragBox({
condition: ol.events.condition.platformModifierKeyOnly,
})
}
/*******
* @description:
* @param {*} options 前台给的配置项
* @return {*}
*/
// 等值线isolines or 等值面isobands
CreateIsolines(options, cb) {
// 传递过来的z 的最大最小值
var min = Math.floor(Math.min.apply(null, options.z) - 2)
var max = Math.ceil(Math.max.apply(null, options.z) + 5)
// 根据z值范围调整
var breaks = []
// 对值进行处理
for (let i = 0, j = min; j < max; i++) {
breaks.push(j)
j += options.interval
}
// isolines --线 isobands--面
let params = {
mapCenter: [116.14, 39.11],
maxValue: 100,
krigingModel: 'spherical', //model还可选'gaussian','spherical',exponential
krigingSigma2: 0,
krigingAlpha: 100,
canvasAlpha: 0.75, //canvas图层透明度
colors: [],
}
// 传递过来的颜色的处理
var blue = []
var green = []
var red = []
var golden = []
var yellowish = []
let str = ""
switch (options.color) {
// 金色
case 'rgba(218,165,32,1)':
for (let i = 0, j = 50; j < 100; i++) {
j += 50 / breaks.length
str = `rgba(218,165,32,${(j / 100).toFixed(2)})`
golden.push(str)
}
params.colors = golden
break;
// 蓝色
case 'rgba(0,0,255,1)':
for (let i = 0, j = 50; j < 100; i++) {
j += 50 / breaks.length
str = `rgba(0,0,255,${(j / 100).toFixed(2)})`
blue.push(str)
}
params.colors = blue
break;
// 绿色
case 'rgba(0,128,0,1)':
for (let i = 0, j = 50; j < 100; i++) {
j += 50 / breaks.length
str = `rgba(0,128,0,${(j / 100).toFixed(2)})`
green.push(str)
}
params.colors = green
break;
// 红色
case 'rgba(255,0,0,1)':
for (let i = 0, j = 50; j < 100; i++) {
j += 50 / breaks.length
str = `rgba(255,0,0,${(j / 100).toFixed(2)})`
red.push(str)
}
params.colors = red
break;
// 淡黄色
case 'rgba(240,230,140,1)':
for (let i = 0, j = 50; j < 100; i++) {
j += 50 / breaks.length
str = `rgba(240,230,140,${(j / 100).toFixed(2)})`
yellowish.push(str)
}
params.colors = yellowish
break;
default:
break;
}
// 返回给前端页面的
var result = {
legend: [
]
}
self.WFSVectorSource = new ol.source.Vector()
self.WFSVectorLayer = new ol.layer.Vector({
source: self.WFSVectorSource,
})
self.map.addLayer(self.WFSVectorLayer)
//添加选择和框选控件,按住Ctr键,使用鼠标框选采样点
self.map.addInteraction(self.select)
self.map.addInteraction(self.dragBox)
//设置框选事件
let selectedFeatures = self.select.getFeatures()
self.dragBox.on('boxend', () => {
let extent = self.dragBox.getGeometry().getExtent()
self.WFSVectorSource.forEachFeatureIntersectingExtent(extent, (feature) => {
selectedFeatures.push(feature)
})
drawKriging(extent)
})
self.dragBox.on('boxstart', () => {
selectedFeatures.clear()
})
//利用网格计算点集
const gridFeatureCollection = function (grid, xlim, ylim) {
var range = grid.zlim[1] - grid.zlim[0]
var i, j, x, y, z
var n = grid.length //列数
var m = grid[0].length //行数
var pointArray = []
for (i = 0; i < n; i++)
for (j = 0; j < m; j++) {
x = i * grid.width + grid.xlim[0]
y = j * grid.width + grid.ylim[0]
z = grid[i][j]
// z = (grid[i][j] - grid.zlim[0]) / range
// if (z < 0.0) z = 0.0
// if (z > 1.0) z = 1.0
pointArray.push(
turf.point([x, y], {
value: z,
})
)
}
return pointArray
}
//绘制kriging插值图
const drawKriging = (extent) => {
let values = options.z,
lngs = options.x,
lats = options.y
if (values.length > 3) {
let variogram = kriging.train(
values,
lngs,
lats,
params.krigingModel,
params.krigingSigma2,
params.krigingAlpha
)
let polygons = []
polygons.push([
[extent[0], extent[1]],
[extent[0], extent[3]],
[extent[2], extent[3]],
[extent[2], extent[1]],
])
let grid = kriging.grid(polygons, variogram, (extent[2] - extent[0]) / 500)
let dragboxExtent = extent
if (self.vectorLayer !== null) {
self.map.removeLayer(self.vectorLayer)
}
self.vectorSource = new ol.source.Vector()
self.vectorLayer = new ol.layer.Vector({
source: self.vectorSource,
opacity: 0.9,
style: function (feature) {
var style = new ol.style.Style({
text: new ol.style.Text({
text: feature.get('value') + '',
scale: 1.5,
}),
})
// 判断是等值线 还是 等值面
if (options.type == 'isobands') {
let ind = breaks.indexOf(parseFloat(feature.get('value').split('-')))
style.fill_ = new ol.style.Fill({
color: params.colors[ind],
})
result.type = "等值面"
result.legend.push(
{
label: feature.get('value'),
value: params.colors[ind]
}
)
} else {
//等值线
style.stroke_ = new ol.style.Stroke({
color: '#000',
})
result.type = "等值线"
}
return style
},
})
//使用turf渲染等值面/线
let fc = gridFeatureCollection(grid, [extent[0], extent[2]], [extent[1], extent[3]])
var collection = turf.featureCollection(fc)
// 要生成的等值效果
var isobands
if (options.type == 'isobands') {
//等值线
isobands = turf.isobands(collection, breaks, {
zProperty: 'value',
})
} else {
//等值面
isobands = turf.isolines(collection, breaks, {
zProperty: 'value',
})
}
function sortArea(a, b) {
return turf.area(b) - turf.area(a)
}
// -------裁剪工具类start---------
let features = [];
isobands.features.forEach(function (layer1) {
geojsonObject.features.forEach(function (layer2) {
let intersection = null;
try {
intersection = turf.intersect(layer1, layer2);
} catch (e) {
layer1 = turf.buffer(layer1, 0);
intersection = turf.intersect(layer1, layer2);
}
if (intersection != null) {
intersection.properties = layer1.properties;
intersection.id = Math.random() * 100000;
features.push(intersection);
}
});
});
let intersections = turf.featureCollection(features);
//-------------裁剪工具类end======
//按照面积对图层进行排序,规避turf的一个bug
intersections.features.sort(sortArea)
var polyFeatures = new ol.format.GeoJSON().readFeatures(intersections, {
featureProjection: 'EPSG:4326',
})
self.vectorSource.addFeatures(polyFeatures)
self.map.addLayer(self.vectorLayer)
} else {
alert('有效样点个数不足,无法插值')
}
}
// 边界的范围
let extent = options.extent
self.WFSVectorSource.forEachFeatureIntersectingExtent(extent, (feature) => {
selectedFeatures.push(feature)
})
drawKriging(extent)
cb(result)//返回结果
// 返回格式
/**
* {
legend: [
{
label: "24-25",
value: "rgba(218,165,32,0.77)"
},
{
label: "23-24",
value: "rgba(218,165,32,0.67)"
},
],
type: "等值面"
}
*/
}
clearIsolines() {
self.map.removeInteraction(self.WFSVectorSource);
self.map.removeInteraction(self.vectorSource);
self.map.removeInteraction(self.dragBox);
self.map.removeInteraction(self.select);
self.map.removeLayer(self.vectorLayer)
self.map.removeLayer(self.WFSVectorLayer)
}
}
生成等值线、面的图例返回到页面上生成图例
- html
<div class="sample">
<a-button type="primary" style="padding: 3px 20px; margin-right: 10px" @click="MapImExport">导出</a-button>
<a-table
:row-selection="rowSelection"
:columns="columns"
:data-source="datas"
:rowKey="(record) => record.GCEABC"
:pagination="false"
>
</a-table>
</div>
<a-modal
title="设置"
:width="900"
:visible="AnalysisVisible"
@ok="AnalysishandleSubmit"
@cancel="AnalysishandleCancel"
>
<div style="margin-top: 20px">
<span style="width: 120px; display: inline-block; text-align: right; margin-right: 10px">年份:</span>
<el-select v-model="Analysis.year" placeholder="请选择">
<el-option v-for="item in AnalysisYear" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
</div>
<div style="margin-top: 20px">
<span style="width: 120px; display: inline-block; text-align: right; margin-right: 10px">月份:</span>
<el-select v-model="Analysis.month" placeholder="请选择">
<el-option v-for="item in AnalysisMonth" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
</div>
<div style="margin-top: 20px">
<span style="width: 120px; display: inline-block; text-align: right; margin-right: 10px">等值面颜色设定:</span
><color-select :color-list="colorList" v-model="settings.color"></color-select>
</div>
<div style="margin-top: 20px">
<span style="width: 120px; display: inline-block; text-align: right; margin-right: 10px">间隔设定:</span>
<el-select v-model="settings.interval" placeholder="请选择">
<el-option v-for="item in intervaloptions" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
</div>
<div style="margin-top: 20px">
<span style="width: 120px; display: inline-block; text-align: right; margin-right: 10px">生成类型:</span>
<el-button-group>
<el-button :type="type1" @click="typeSele('isobands')">等值面</el-button>
<el-button :type="type2" @click="typeSele('isolines')">等值线 </el-button>
</el-button-group>
</div>
</a-modal>
- js
// 分析
AnalysishandleSubmit() {
this.Analysis.uniCodeList = this.selectedRowKeys
if (this.Analysis.uniCodeList.length < 3) {
this.$message.success('选择的井不足三个无法生成等值线')
return
} else {
postAnslysisContour(this.Analysis).then((res) => {
let obj = Object.assign(this.settings, res.data.contourData)
this.$refs.map.CreateEquivalence(obj)
})
}
this.AnalysisVisible = false
},
// 取消
AnalysishandleCancel() {
this.AnalysisVisible = false
},
//导出调用Mapgis.vue
MapImExport() {
this.$refs.map.MapImExport()
},
- css
.sample {
height: 268px;
overflow: auto;
}
MapGis.vue页面通过html2canvas生成图片,然后通过blob流下载等值线图
下载npm包: npm i html2canvas@1.2.2
- html
<a-modal v-model="visible" title="等值线预览" @ok="handleOk">
<div id="image" class="Export" style="100%">
<div class="ExportImg">
<div class="ExTit">
<h3>2021年6月</h3>
<p>地下水位等值线图(单位:米)</p>
</div>
<img id="MapImage" :src="MapImg" alt="" style="width: 100%" />
</div>
<div class="IsolineLegend">
<p style="font: bold 16px/26px '微软雅黑'">图例</p>
<div style="display: flex">
<div style="margin-right: 50px">
<h4 style="margin: 10px 0">监测井图例</h4>
<p style="font: 12px/20px '微软雅黑'">
<span class="iconfont icon-zuankong" style="color: rgb(0, 255, 0); margin-right: 10px"></span>人工监测
</p>
<p style="font: 12px/20px '微软雅黑'">
<span class="iconfont icon-zuankong" style="color: rgb(255, 0, 0); margin-right: 10px"></span>自动监测
</p>
<p style="font: 12px/20px '微软雅黑'">
<span class="iconfont icon-zuankong" style="color: rgb(152 150 150); margin-right: 10px"></span>设备离线
</p>
</div>
<div>
<h4 style="margin: 10px 0">{{ IsolineLegend.type }}图例</h4>
<ul style="width: 320px; display: flex; flex-wrap: wrap" v-if="IsolineLegend.type == '等值面'">
<li
v-for="(item, ind) in IsolineLegend.legend"
:key="ind"
style="font: 14px/20px '微软雅黑'; margin-right: 20px"
>
<span
:style="{
background: item.value,
'margin-right': '15px',
width: '30px',
height: '20px',
display: 'inline-block',
}"
></span
>{{ item.label }}
</li>
</ul>
<p v-else>
<span style="margin-right: 10px; display: inline-block; width: 30px; height: 3px;color:#000">——</span>等值线
</p>
</div>
</div>
</div>
</div>
</a-modal>
- js
data() {
return {
mapgis: {}, //地图实例
MapImg: '', //生成图片的地址
visible: false, //预览开关
wholeExtent: [], //范围
IsolineLegend: {}, //等值线需要的图例结果
}
},
// 生成等值线
CreateEquivalence(data) {
data.extent = this.wholeExtent
this.Mapgiss.QueryToolClear()
this.Mapgiss.CreateEquivalence(data, (res) => {
this.IsolineLegend = res
})
},
methods: {
//等值线导出
MapImExport() {
this.visible = true
var that = this
// 地图截图
this.mapgis.once('postcompose', function (event) {
//获取map中的canvas,并转换为图片
var canvas = event.context.canvas
if (navigator.msSaveBlob) {
navigator.msSaveBlob(canvas.msToBlob(), 'map.png')
} else {
console.log(that)
that.MapImg = canvas.toDataURL('image/png')
// canvas.toBlob(function (blob) {
// // saveAs(blob, 'map.png')
// })
}
})
this.mapgis.renderSync()
},
//保存到本地
handleOk(e) {
//通过html2canvas插件把整个Div生成为片 点击确定实现下载 需要后台传来导出名字
html2canvas(document.getElementById('image')).then(function (canvas) {
var imgUri = canvas.toDataURL('image/jpeg') // 获取生成的图片的url
let el = document.createElement('a')
el.href = imgUri
// 指定下载的文件名
el.download = '等值导出结果.png'
el.hidden = true
document.body.appendChild(el)
el.click()
document.body.removeChild(el)
})
this.visible = false
},
}
- css
.Export {
width: 100%;
position: relative;
}
.Export .ExportImg {
border: 2px solid #999;
}
.Export .ExTit {
border: 1px solid #999;
width: 60%;
position: absolute;
background: #fff;
top: 1px;
left: 1px;
}
.Export .ExTit > h3 {
text-align: center;
padding: 5px 0;
}
.Export .ExTit > p {
text-align: center;
padding: 0 0 0 10px;
}
.Export .IsolineLegend {
background: #fff;
padding: 10px;
}
以上是本人基于openlayers封装的生成等值线或者面生成的可下载为图片的部分代码,基本逻辑已描述。
逻辑步骤:
① 选择要生成的点(点里有xyz三个值)根据里面的z来判断等值间隔
② 通过设置页设置色带,间隔值,等值线/面
③ 生成的等值面通过openlayers内置的截图工具截图出来
④ 生成的地图,图例拼接起来然后通过html2canvas插件把该div导出成图片