重要:博客内使用地图全替换为WMap,想使用的请参考我这篇博客(WMap)
最近在做矢量图形这一块,发现圆形和多边形,都可以使用openlayers自带的Modify类进行编辑,然后随意放大缩小旋转,但是 矩形尤为特别,因为矩形属于Polygon, 也是多边形的一种, 但是却实现不了矩形的编辑,为此,我以下做了最全的矢量图 主动绘制、根据坐标点绘制和编辑 的 功能, 有不懂的可以评论哦,我一般都会看的~
(因为是我自己封装的类,我就不把全部代码发出来,就发关键代码,下面有些代码你不知道做什么的,可以和我说下喔~)
根据已有坐标点绘制矩形、圆、多边形、点、线:
矩形实例化是通过下列方式生成:
// 根据已有坐标生成矩形
// path 为传进来的坐标数组 [minX, minY, maxX, maxY]
// 通俗说来就是西南顶点的经纬度和东北顶点的经纬度
// extData 是自定义数据
import { fromExtent } from 'ol/geom/Polygon'
/**
* 矩形
*/
点、线、矩形、圆形和多边形是通过这种方式生成:
// formatMetersToRadius 方法可以参考我前面的文章 踩坑系列二
// fromLonLat 是用来经纬度转换的,因为实际经纬度和地图经纬度存在误差
// commonStyle 是我自己封装的通用样式方法, 其就是返回new Style的函数
// extData 是我想在图形中添加一些自定义的数据, 需要你才加这行代码
// OlFeature类是继承于Feature的自定义类
// this.olTarget 是OlFeature类的实例属性,用来代替每个实例化的图形的,
// 因为我这里图形实例,是通过调用方法形式来的,下面的都是方法,而不是类,
// 当然如果你想做成和高德地图一样的,那就要写成内部类
import { fromLonLat } from 'ol/proj'
// 点绘制
Point (center, option = {}, extData = {}) {
let centerPath = fromLonLat([center[0], center[1]], 'EPSG:4326')
const tmp = new Point(centerPath)
this.olTarget = new OlFeature(tmp)
this.olTarget.set('extData', extData)
if (extData.id) {
this.olTarget.setId(extData.id)
} else {
this.olTarget.setId(this.olTarget.ol_uid)
}
this.olTarget.setStyle(this.commonStyle(option))
this.graphTool.Point.style = this.commonStyle(option)
this.source.addFeature(this.olTarget)
return this
}
// 线段绘制
Line (path, option = {}, extData = {}) {
const finalPath = []
// 进行坐标系转换 转4326坐标系
for (let i = 0; i < path.length; i++) {
const tmpPoint = fromLonLat([path[i][0], path[i][1]], 'EPSG:4326')
finalPath.push(tmpPoint)
}
const tmp = new LineString(finalPath)
this.olTarget = new OlFeature({
geometry: tmp
})
this.olTarget.set('extData', extData)
if (extData.id) {
this.olTarget.setId(extData.id)
} else {
this.olTarget.setId(this.olTarget.ol_uid)
}
this.olTarget.setStyle(this.commonStyle(option))
this.graphTool.Line.style = this.commonStyle(option)
this.source.addFeature(this.olTarget)
return this
}
// 矩形绘制
Rectangle (path, option = {}, extData = {}) {
const tmpArr = []
if (path.length !== 4) {
console.warn('WMap 图形类警告: 传入路径错误,请检查~')
return
}
tmpArr[0] = path.slice(0, path.length / 2)
tmpArr[1] = path.slice(-(path.length / 2))
const finalPath = []
// 进行坐标系转换 转4326坐标系
for (let i = 0; i < tmpArr.length; i++) {
const tmpPoint = fromLonLat([tmpArr[i][0], tmpArr[i][1]], 'EPSG:4326')
finalPath.push(tmpPoint)
}
const tmp = fromExtent(finalPath.flat())
this.olTarget = new OlFeature({
geometry: tmp
})
this.olTarget.set('extData', extData)
if (extData.id) {
this.olTarget.setId(extData.id)
} else {
this.olTarget.setId(this.olTarget.ol_uid)
}
this.olTarget.setStyle(this.commonStyle(option))
this.graphTool.Rectangle.style = this.commonStyle(option)
this.source.addFeature(this.olTarget)
return this
}
// 圆形绘制
Circle (center, radius, option = {}, extData = {}) {
// 进行坐标系转换 转4326坐标系
const finalPath = fromLonLat([center[0], center[1]], 'EPSG:4326')
const finalRadius = this.formatMetersToRadius(radius)
const tmp = new Circle(finalPath, finalRadius)
this.olTarget = new OlFeature(tmp)
this.olTarget.set('extData', extData)
if (extData.id) {
this.olTarget.setId(extData.id)
} else {
this.olTarget.setId(this.olTarget.ol_uid)
}
this.olTarget.setStyle(this.commonStyle(option))
this.graphTool.Circle.style = this.commonStyle(option)
this.source.addFeature(this.olTarget)
return this
}
// 多边形绘制
Polygon (path, option = {}, extData = {}) {
const finalPath = []
// 进行坐标系转换 转4326坐标系
for (let i = 0; i < path.length; i++) {
const tmpPoint = fromLonLat([path[i][0], path[i][1]], 'EPSG:4326')
finalPath.push(tmpPoint)
}
const tmp = new Polygon([finalPath])
this.olTarget = new OlFeature(tmp)
this.olTarget.set('extData', extData)
if (extData.id) {
this.olTarget.setId(extData.id)
} else {
this.olTarget.setId(this.olTarget.ol_uid)
}
this.olTarget.setStyle(this.commonStyle(option))
this.graphTool.Polygon.style = this.commonStyle(option)
this.source.addFeature(this.olTarget)
return this
}
主动绘制矩形、圆、多边形
// 开始主动绘制的方法
// dispatchEvent 这里使用了浏览器的事件派发,有不懂的可以问下我
this.beginPaint = function (type) {
let geometryFunction // 更新几何坐标时调用的函数。
let penValue = 'Circle' // 笔尖类型 默认为Circle
switch (type) {
case 'Point':
penValue = 'Point'
break
case 'Line':
penValue = 'LineString'
break
// 默认不写参数 即为圆形
case 'Circle':
break
case 'Polygon':
penValue = 'Polygon'
break
case 'Rectangle':
geometryFunction = this.paintRectangle()
break
case 'Square':
geometryFunction = this.paintSquare()
break
}
if (this.graphTool[type].style) {
this.vector.setStyle(this.graphTool[type].style)
}
let freehandFlag = false
switch (type) {
case 'Polygon':
case 'Line':
freehandFlag = false
break
default:
freehandFlag = true
break
}
this.drawTarget = new OlDraw({
// 数据源
source: this.source,
// 绘制类型
type: penValue,
geometryFunction: geometryFunction,
freehand: freehandFlag, // 手绘模式
stopClick: true
})
// 将draw对象添加到map中,然后就可以进行图形绘制了
this.map.addGraph(this.drawTarget)
this.drawTarget.setActive(true)
this.drawTarget.addEventListener('drawend', e => {
// 将当前绘制的矢量图形 通过done事件触发抛出去
let targets = this.WEvents.get('WGraph(done)')
this.drawFeature = this.buildFeature(e.feature)
e.feature.set('WTYPE', 'OlDraw')
window.dispatchEvent(targets.costomEvent)
})
}
/**
* 绘制矩形
*/
paintRectangle () {
return createBox()
}
/**
* 绘制正方形
* @param {*} params
*/
paintSquare () {
return createRegularPolygon(4)
}
编辑点、线、矩形、圆、多边形:
这里编辑多边形和圆都很简单,其他类似的博客或帖子都用了 Select和Modify来编辑,但是我在编辑矩形时,发现矩形居然和多边形一样,可以随意改变,但是我们生成矩形时 fromExtent 就限定了只能传 [minX, minY, maxX, maxY] 这种坐标数组, 而不是像多边形一样,把所有坐标点传上去,所以就要按我下面的这种方式来编辑图形
(当类型为线、多边形(矩形除外)、圆形时,允许新增点, 类型为点、矩形时,就利用克隆图形,不新增点)
import { always, never, platformModifierKeyOnly, primaryAction } from 'ol/events/condition'
/**
* 计算矢量图形中心点相关参数
* @param {*} geometry
* @returns
*/
const calculateCenter = geometry => {
let center, coordinates, minRadius
const type = geometry.getType()
if (type === 'Polygon') {
let x = 0
let y = 0
let i = 0
coordinates = geometry.getCoordinates()[0].slice(1)
coordinates.forEach(function(coordinate) {
x += coordinate[0]
y += coordinate[1]
i++
})
center = [x / i, y / i]
} else if (type === 'LineString') {
center = geometry.getCoordinateAt(0.5)
coordinates = geometry.getCoordinates()
} else {
center = getCenter(geometry.getExtent())
}
let sqDistances
if (coordinates) {
sqDistances = coordinates.map(function(coordinate) {
const dx = coordinate[0] - center[0]
const dy = coordinate[1] - center[1]
return dx * dx + dy * dy
})
minRadius = Math.sqrt(Math.max.apply(Math, sqDistances)) / 3
} else {
minRadius = Math.max(getWidth(geometry.getExtent()), getHeight(geometry.getExtent())) / 3
}
return {
center: center,
coordinates: coordinates,
minRadius: minRadius,
sqDistances: sqDistances
}
}
// 判断图形类型
/**
* 判断矢量图形形状
* e 等同于 feature.getGeometry()
*/
judgeShape (e) {
let type = e.getType()
if (type === 'Polygon') {
let arr = e.getCoordinates().flat()
if (arr.length === 5) {
const rect = arr.slice(0, 4)
if ((rect[0][0] === rect[1][0] && rect[0][1] === rect[3][1]) || (rect[0][1] === rect[1][1] && rect[0][0] === rect[3][0])) {
type = 'Rectangle'
}
}
} else if (type === 'LineString') {
type = 'Line'
}
return type
}
/**
* 编辑矢量图形
*/
editPaint () {
const defaultStyle = new Modify({ source: this.source }).getOverlay().getStyleFunction()
let _this = this
const modify = new Modify({
source: this.source,
condition: function (event) {
return primaryAction(event) && !platformModifierKeyOnly(event)
},
deleteCondition: never,
insertVertexCondition: always,
style: function (feature) {
let type = _this.judgeShape(feature.get('geometries')[0])
if (type !== 'Line'){
feature.get('features').forEach(function (modifyFeature) {
const modifyGeometry = modifyFeature.get('modifyGeometry')
if (modifyGeometry) {
const point = feature.getGeometry().getCoordinates()
let modifyPoint = modifyGeometry.point
if (!modifyPoint) {
// save the initial geometry and vertex position
modifyPoint = point
modifyGeometry.point = modifyPoint
modifyGeometry.geometry0 = modifyGeometry.geometry
// get anchor and minimum radius of vertices to be used
const result = _this.calculateCenter(modifyGeometry.geometry0)
modifyGeometry.center = result.center
modifyGeometry.minRadius = result.minRadius
}
const center = modifyGeometry.center
const minRadius = modifyGeometry.minRadius
let dx, dy
dx = modifyPoint[0] - center[0]
dy = modifyPoint[1] - center[1]
const initialRadius = Math.sqrt(dx * dx + dy * dy)
if (initialRadius > minRadius) {
const initialAngle = Math.atan2(dy, dx)
dx = point[0] - center[0]
dy = point[1] - center[1]
const currentRadius = Math.sqrt(dx * dx + dy * dy)
if (currentRadius > 0) {
const currentAngle = Math.atan2(dy, dx)
const geometry = modifyGeometry.geometry0.clone()
geometry.scale(currentRadius / initialRadius, undefined, center)
const typeName = modifyGeometry.geometry0.getType()
if (typeName === 'Polygon') {
let shapeName = _this.judgeShape(modifyGeometry.geometry0)
if (shapeName !== 'Rectangle') {
geometry.rotate(currentAngle - initialAngle, center)
}
} else if (typeName === 'Circle') {
geometry.translate(dx, dy)
} else if (typeName === 'Point') {
geometry.translate(dx, dy)
}
modifyGeometry.geometry = geometry
}
}
}
})
}
return defaultStyle(feature)
}
})
this.map.addGraph(modify)
const trans = new Translate({
condition: function (event) {
return primaryAction(event) && platformModifierKeyOnly(event)
},
layers: [this.vector]
})
this.map.addGraph(trans)
modify.on('modifystart', e => {
let _this = this
e.features.forEach(function (feature) {
let shape = _this.judgeShape(feature.getGeometry())
if (shape === 'Line' || shape === 'Polygon' || shape === 'Circle') {
// 当类型为线、多边形(矩形除外)、圆形时,允许新增点
} else {
// 克隆图形,不能新增点
feature.set('modifyGeometry', { geometry: feature.getGeometry().clone() }, true)
}
})
})
modify.on('modifyend', e => {
let _this = this
e.features.forEach(function (feature) {
const modifyGeometry = feature.get('modifyGeometry')
if (modifyGeometry) {
feature.setGeometry(modifyGeometry.geometry)
feature.unset('modifyGeometry', true)
}
let targets = _this.WEvents.get('WGraph(modifyend)')
_this.drawFeature = _this.buildFeature(feature)
feature.set('WTYPE', 'OlDraw')
window.dispatchEvent(targets.costomEvent)
})
})
}