import { Zondy, Evented, SketchDataType, Projection } from '../../base'
import { defaultValue, Log } from '../../util'
import {
Point,
LineString,
Polygon,
SpatialReference,
Circle,
Extent
} from '../../base/geometry'
import SketchStyle from './SketchStyle'
import SketchPointDrawTool from './SketchPointDrawTool'
import SketchPolygonDrawTool from './SketchPolygonDrawTool'
import SketchSnappingTool from './SketchSnappingTool'
import SketchPolylineDrawTool from './SketchPolylineDrawTool'
import SketchCircleDrawTool from './SketchCircleDrawTool'
import SketchExtentDrawTool from './SketchExtentDrawTool'
import SketchTopologyTool from './SketchTopologyTool'
import UndoRedoManager from './UndoRedoManager'
import SketchPolylineToPolygonDrawTool from './SketchPolylineToPolygonDrawTool'
/**
* 草图编辑基类
* @class SketchEditor
* @moduleEX SketchEditorModule
* @extends Evented
* @param {Object} options 构造参数
* @param {MapView|SceneView} [options.view] 地图视图对象
* @param {GraphicsLayer} [options.layer] 草图图层管对象
* @param {SketchStyle} [options.sketchStyle] 草图符号
* @param {Object} [options.snapOption] 草图捕获配置项
*
* @summary <h5>支持如下方法:</h5>
* <a href='#start'>[1、开始绘制草图]</a><br/>
* <a href='#stop'>[2、停止绘制]</a><br/>
* <a href='#remove'>[3、移除当前草图]</a><br/>
* <a href='#update'>[4、更新当前草图]</a><br/>
* <a href='#addVertex'>[5、向当前线或面草图中插入新的顶点]</a><br/>
* <a href='#updateVertex'>[6、更新草图图形的某个顶点]</a><br/>
* <a href='#removeVertex'>[7、移除草图图形的某个顶点]</a><br/>
* <a href='#getSketchDataType'>[8、获取草图图形类型]</a><br/>
* <a href='#setSketchStyle'>[9、设置草图样式]</a><br/>
* <a href='#getSketchStyle'>[10、获取草图样式]</a><br/>
* <a href='#getGeometry'>[11、获取草图几何对象]</a><br/>
* <a href='#union'>[12、合并多个区几何]</a><br/>
* <a href='#split'>[13、分割草图对象或区几何对象]</a><br/>
* <a href='#undo'>[14、撤销当前编辑操作]</a><br/>
* <a href='#redo'>[15、恢复被撤销的草图]</a><br/>
* <a href='#drawPolylineToPolygon'>[16、拓扑线造区]</a><br/>
* @example
* // ES5引入方式
* const { SketchEditor } = Zondy
* // ES6引入方式
* import { SketchEditor } from "@mapgis/webclient-common"
*/
/**
* @event SketchEditor#草图绘制完成事件
* @property {Object} event 事件对象
* @property {LayerEventType} [event.type = 'drawn'] 事件类型
* @property {Geometry} [event.geometry] 绘制的几何对象
* @example <caption><h7>鼠标绘制完成事件</h7></caption>
* sketchEditor.on('drawn', (event) => {
* console.log("绘制的几何对象:", event.geometry)
* })
*/
/**
* @event SketchEditor#标绘制线或区的一个顶点完成事件
* @property {Object} event 事件对象
* @property {LayerEventType} [event.type = 'drawn-vertex'] 事件类型
* @property {Geometry} [event.geometry] 绘制的几何对象
* @example <caption><h7>鼠标绘制线或区的一个顶点完成事件</h7></caption>
* sketchEditor.on('drawn-vertex', (event) => {
* console.log("绘制的几何对象:", event.geometry)
* })
*/
/**
* @event SketchEditor#草图被选中事件
* @property {Object} event 事件对象
* @property {LayerEventType} [event.type = 'selected'] 事件类型
* @property {SketchEditorLeaflet|SketchEditorCesium} [event.selectedSketch] 被选中的草图对象
* @example <caption><h7>草图被鼠标选中事件</h7></caption>
* sketchEditor.on('selected', (event) => {
* console.log("被选中事件:", event.selectedSketch)
* })
*/
class SketchEditor extends Evented {
constructor(options) {
super()
options = defaultValue(options, {})
if (!options.view) {
Log.error('options.view is null!', options)
}
/**
* 地图视图
* @member {MapView|SceneView} SketchEditor.prototype.view
*/
this.view = options.view
this._spatialReference = options.view.crs
? new SpatialReference(options.view.crs.code)
: new SpatialReference('EPSG:4326')
/**
* 草图图层
* @member {GraphicsLayer} SketchEditor.prototype.layer
*/
this.layer = defaultValue(options.layer, undefined)
/**
* 草图符号
* @member {SketchStyle} SketchEditor.prototype.sketchStyle
*/
this.sketchStyle = defaultValue(options.sketchStyle, new SketchStyle())
/**
* 草图是否可编辑
* @member {SketchStyle} SketchEditor.prototype._editable
*/
this._editable = defaultValue(options.editable, true)
/**
* 草图捕获配置项
* @member {Object} [SketchEditor.prototype.snapOption = undefined]
* @param {Boolean} [snapOption.isSnapVertexCoincident = false] 是否自动捕捉顶点重合
* @param {Boolean} [snapOption.isSnapVertexInLine = false] 是否自动捕捉线上的点
* @param {Boolean} [snapOption.isSnapPerpendicular = false] 是否自动捕捉垂线垂点
* @param {Boolean} [snapOption.isSnapParallel = false] 是否自动捕捉平行线
* @param {Boolean} [snapOption.snapSketchGeometry = false] 是否捕捉正在绘制的图形的边界
*/
this.snapOption = defaultValue(options.snapOption, {})
/**
* 捕获和线造区边界参考几何图形集合
* @member {Number} SketchEditor.prototype.snapAndReferGeometries
*/
this.snapAndReferGeometries = defaultValue(
options.snapAndReferGeometries,
[]
)
/**
* 草图量算配置项
* @member {Number} SketchEditor.prototype.measureOption
*/
this.measureOption = defaultValue(options.measureOption, undefined)
/**
* 当前选中草图绘制工具
* @member {Number} SketchEditor.prototype._drawTool
*/
this._drawTool = null
/**
* 草图绘制类型
* @member {Number} SketchEditor.prototype._sketchDataType
*/
this._sketchDataType = null
/**
* 草图捕获工具
* @member {Number} SketchEditor.prototype._sketchDataType
*/
this._sketchSnappingTool = {}
/**
* 草图拓扑分析工具
* @member {Number} SketchEditor.prototype._sketchTopologyTool
*/
this._sketchTopologyTool = {}
/**
* 草图撤销回退管理器
* @member {Strign} SketchEditor.prototype.undoRedoManager
*/
this._undoRedoManager = new UndoRedoManager()
this._editorState = {
editMode: 0,
undoRedoManager: this.undoRedoManager,
_hitTestEvent: this._hitTestEvent
}
if (this.layer) {
const layerExist = this.view._map.findLayerById(this.layer.id)
if (!layerExist) {
this.view._map.add(this.layer)
const self = this
this.layer.on('layerview-created', function (result) {
self._layerView = result.layerView
self._hitTestSketchEvent()
})
}
// 有图层存在,初始化捕捉和拓扑分析工具
this._sketchSnappingTool = new SketchSnappingTool(
Object.assign(this.snapOption, {
view: this.view
})
)
this._sketchTopologyTool = new SketchTopologyTool()
} else {
// 没有图层存在,草图捕获置为空
this._hitTestSketchEvent()
}
}
/**
* 开始(鼠标)绘制草图<a id='start'></a>
* 根据传入绘制草图类型,开始鼠标绘制。<br/>
* 绘制点图形,鼠标单击即绘制。<br/>
* 绘制线图形,鼠标单击绘制线的一个顶点;鼠标移动,延长线图形;鼠标双击,完成线图形绘制。<br/>
* 绘制区图形,鼠标单击绘制区的一个顶点;鼠标移动,区图形随鼠标位置变动;鼠标双击,完成区图形绘制。<br/>
* @param {SketchDataType} dataType 绘制的草图类型
*/
start(data) {
if (this._drawTool) {
this._drawTool._removeEditGraphics()
this._drawTool._stopAll()
this._drawTools = []
this._undoRedoManager.reset()
}
let dataType = ''
let isGeometry = false
if (typeof data === 'number') {
dataType = data
} else if (data instanceof Point) {
dataType = SketchDataType.POINT
isGeometry = true
} else if (data instanceof LineString) {
dataType = SketchDataType.POLYLINE
isGeometry = true
} else if (data instanceof Polygon) {
dataType = SketchDataType.POLYGON
isGeometry = true
} else if (data instanceof Circle) {
dataType = SketchDataType.CIRCLE
isGeometry = true
} else if (data instanceof Extent) {
dataType = SketchDataType.RECTANGLE
isGeometry = true
}
switch (dataType) {
case SketchDataType.POINT:
// 新建草图点实例
this._drawTool = new SketchPointDrawTool({
view: this.view,
sketchStyle: this.sketchStyle,
layer: this.layer,
undoRedoManager: this._undoRedoManager,
_sketchEditor: this
})
break
case SketchDataType.POLYLINE:
// 新建草图线实例
this._drawTool = new SketchPolylineDrawTool({
view: this.view,
sketchStyle: this.sketchStyle,
layer: this.layer,
undoRedoManager: this._undoRedoManager,
_sketchEditor: this
})
break
case SketchDataType.POLYGON:
// 新建草图区实例
this._drawTool = new SketchPolygonDrawTool({
view: this.view,
sketchStyle: this.sketchStyle,
layer: this.layer,
undoRedoManager: this._undoRedoManager,
_sketchEditor: this
})
break
case SketchDataType.CIRCLE:
// 新建草图区实例
this._drawTool = new SketchCircleDrawTool({
view: this.view,
sketchStyle: this.sketchStyle,
layer: this.layer,
undoRedoManager: this._undoRedoManager,
_sketchEditor: this
})
break
case SketchDataType.RECTANGLE:
// 新建草图区实例
this._drawTool = new SketchExtentDrawTool({
view: this.view,
sketchStyle: this.sketchStyle,
layer: this.layer,
undoRedoManager: this._undoRedoManager,
_sketchEditor: this
})
break
default:
break
}
if (this._drawTool) {
this._drawTool._sketchDataType = dataType
if (isGeometry) {
this._addGeometry(data)
this._drawTool.state = 'drawn'
} else {
this._drawTool.start(this._sketchDataType)
}
// 响应草图被选中事件
this._selectEvent()
this._drawEvent()
}
}
_addFeatureToSketchEditor(feature, isShow) {
const geometry = feature.geometry
let dataType = undefined
if (geometry instanceof Point) {
dataType = SketchDataType.POINT
} else if (geometry instanceof LineString) {
dataType = SketchDataType.POLYLINE
} else if (geometry instanceof Polygon) {
dataType = SketchDataType.POLYGON
} else if (geometry instanceof Circle) {
dataType = SketchDataType.CIRCLE
} else if (geometry instanceof Extent) {
dataType = SketchDataType.RECTANGLE
}
switch (dataType) {
case SketchDataType.POINT:
// 新建草图点实例
this._drawTool = new SketchPointDrawTool({
view: this.view,
sketchStyle: this.sketchStyle,
layer: this.layer,
undoRedoManager: this._undoRedoManager,
_sketchEditor: this
})
break
case SketchDataType.POLYLINE:
// 新建草图线实例
this._drawTool = new SketchPolylineDrawTool({
view: this.view,
sketchStyle: this.sketchStyle,
layer: this.layer,
undoRedoManager: this._undoRedoManager,
_sketchEditor: this
})
break
case SketchDataType.POLYGON:
// 新建草图区实例
this._drawTool = new SketchPolygonDrawTool({
view: this.view,
sketchStyle: this.sketchStyle,
layer: this.layer,
undoRedoManager: this._undoRedoManager,
_sketchEditor: this
})
break
case SketchDataType.CIRCLE:
// 新建草图区实例
this._drawTool = new SketchCircleDrawTool({
view: this.view,
sketchStyle: this.sketchStyle,
layer: this.layer,
undoRedoManager: this._undoRedoManager,
_sketchEditor: this
})
break
case SketchDataType.RECTANGLE:
// 新建草图区实例
this._drawTool = new SketchExtentDrawTool({
view: this.view,
sketchStyle: this.sketchStyle,
layer: this.layer,
undoRedoManager: this._undoRedoManager,
_sketchEditor: this
})
break
default:
break
}
this._drawTool.sketchStage.entityGraphic = feature
this._drawTool.state = 'drawn'
// 响应草图被选中事件
this._selectEvent()
this._drawEvent()
if (isShow) {
this._drawTool._addFeaturesToMap(feature)
}
}
/**
* 停止鼠标绘制草图<a id='stop'></a>
*/
stop() {
if (this._drawTool) {
this._drawTool.stop()
if (this._drawTool._editMode === 3) {
this.remove()
}
}
}
/**
* 移除当前草图<a id='remove'></a>
*/
remove() {
this._drawTool.removeDrawTool()
this._drawTool = null
}
/**
* 更新当前草图<a id='update'></a>
* @private
* @param {Object} data 更新的数据
* @param {Number} index 新增/新增点的序号
*/
update(data, featureId) {
this._drawTool.updateFeature(data, featureId)
}
/**
* 向当前线或区草图图形中插入新的顶点
* @private
* @param {Point} point 新增/插入顶点
* @param {Number} index 新增/新增点的序号
*/
_addGeometry(geometry) {
this._drawTool.addFeatureByGeometry(geometry)
}
/**
* 向当前线或区草图中插入新的顶点<a id='addVertex'></a>
* @param {Point} point 新增/插入顶点
* @param {Number} index 新增/新增点的序号
* @returns {Geometry} 改变后的图形几何
*/
addVertex(point, index) {
const geometry = this._drawTool.addVertex(point, index)
return geometry
}
/**
* 更新当前选中或区草图图形的某个顶点<a id='updateVertex'></a>
* @param {Point} point 新的顶点
* @param {Number} index 需更新的顶点的序号
* @returns {Geometry} 改变后的图形几何
*/
updateVertex(point, index) {
const geometry = this._drawTool.updateVertex(point, index)
return geometry
}
/**
* 移除草图图形的某个顶点<a id='removeVertex'></a>
* @param {Number} index 需更新的顶点的序号
* @returns {Geometry} 改变后的图形几何
*/
removeVertex(index) {
const geometry = this._drawTool.removeVertex(index)
return geometry
}
/**
* 获取草图图形类型<a id='getSketchDataType'></a>
* @returns {SketchDataType}
*/
getSketchDataType() {
return this._sketchDataType
}
/**
* 设置草图样式<a id='setSketchStyle'></a>
* @param {SketchStyle} sketchStyle
*/
setSketchStyle(sketchStyle) {
this.sketchStyle = sketchStyle
}
/**
* 获取草图样式<a id='getSketchStyle'></a>
* @returns {SketchStyle}
*/
getSketchStyle() {
return this.sketchStyle
}
/**
* 清空全部草图
* @private
*/
clearAllSketch() {
this.view._map.remove(this.layer)
this._drawTool = null
// 临时测试用
this.view._map.layers.forEach((layer) => {
if (layer.graphics) {
this.view._map.remove(layer)
}
})
}
/**
* 合并多个区几何<a id='union'></a>
* @param {Polygon} polygons 被合并的区几何对象
* @returns {Polygon} 合并后的几何对象
*/
union(polygons) {
const self = this
const unionPolygon = Projection.project(
this._sketchTopologyTool.unionPolygons(polygons),
this._spatialReference
)
const waitLayerLoaded = () => {
let waitTimer = null
waitTimer = setInterval(() => {
const targetLayer = self.view._mapCollection.filter(
(item) => item.id === self.layer.id
)
if (targetLayer.length > 0) {
clearInterval(waitTimer)
self.start(unionPolygon)
if (self._bindHit) {
self._bindHit(self._drawTool)
}
}
}, 50)
}
waitLayerLoaded()
// 响应草图被选中事件
this._selectEvent()
this._drawEvent()
return unionPolygon
}
/**
* 分割草图对象或区几何对象<a id='split'></a>
* @param {Polygon|SketchEditor} target 被分割的几何/草图对象
* @param {Polyline} splitPolyline 线几何对象
* @returns {Array<Polygon>} 分割后的几何对象
*/
split(target, splitPolyline) {
let polygon = null
const self = this
if (target instanceof Polygon) {
polygon = target
} else if (
target instanceof SketchEditor &&
target.drawTool._sketchDataType === SketchDataType.POLYGON
) {
polygon = target.sketchStage.entityGraphic
} else {
Log.error('传入对象不符合要求,请检查')
return
}
const splitPolygon = function (polygon, splitPolyline) {
const splitPolygons = self._sketchTopologyTool.splitPolygonByPolyline(
polygon,
splitPolyline
)
if (splitPolygons.length > 0) {
// 创建分割后的草图
// self.layer.on('layerview-created', function () {
// splitPolygons.forEach((polygon) => {
// polygon = Projection.project(polygon, self._spatialReference)
// self.start(polygon)
// self._bindHit(self._drawTool)
// })
// })
const waitLayerLoaded = () => {
let waitTimer = null
waitTimer = setInterval(() => {
const targetLayer = self.view._mapCollection.filter(
(item) => item.id === self.layer.id
)
if (targetLayer.length > 0) {
clearInterval(waitTimer)
splitPolygons.forEach((polygon) => {
polygon = Projection.project(polygon, self._spatialReference)
self.start(polygon)
if (self._bindHit) {
self._bindHit(self._drawTool)
}
})
}
}, 50)
}
waitLayerLoaded()
}
return splitPolygons
}
return splitPolygon(polygon, splitPolyline)
}
/**
* 线拓扑造区<a id='drawPolylineToPolygon'></a>
* @param {Array<Polygon>} snapAndReferGeometries 捕获参考几何对象数组
* @returns {Array<Polygon>} 分割后的几何对象
*/
drawPolylineToPolygon(snapAndReferGeometries) {
const self = this
this._drawTool = new SketchPolylineToPolygonDrawTool({
view: this.view,
sketchStyle: this.sketchStyle,
layer: this.layer,
undoRedoManager: this._undoRedoManager,
_sketchEditor: this,
snapAndReferGeometries
})
this._drawTool.start()
this._drawTool.on('drawn', (response) => {
if (response.geometry) {
if (response.geometry instanceof Polygon) {
self.start(response.geometry)
if (self._bindHit) {
self._bindHit(self._drawTool)
}
} else {
response.geometry.coordinates.forEach((coordinate) => {
const polygon = new Polygon({
coordinates: coordinate,
spatialReference: this._spatialReference
})
self.start(polygon)
if (self._bindHit) {
self._bindHit(self._drawTool)
}
})
}
}
})
}
/**
* 撤销当前编辑操作<a id='undo'></a>
* @returns {Geometry} 撤销后的几何对象
*/
undo() {
const stage = this._undoRedoManager.undo()
if (stage) {
this._updateByStage(stage)
return stage.entityGraphic.geometry
} else {
return null
}
}
/**
* 恢复被撤销的草图<a id='redo'></a>
* @returns {Geometry} 恢复后的几何对象
*/
redo() {
const stage = this._undoRedoManager.redo()
if (stage) {
this._updateByStage(stage)
return stage.entityGraphic.geometry
} else {
return null
}
}
/**
* 草图是否可执行撤销操作<a id='canUndo'></a>
* @returns {Boolean}
*/
canUndo() {
return this._undoRedoManager.canUndo()
}
/**
* 草图是否可执行恢复操作<a id='canRedo'></a>
* @returns {Boolean}
*/
canRedo() {
return this._undoRedoManager.canRedo()
}
/**
* 根据sketchStage状态更新草图
* @private
* @param {SketchStage} stage 被合并的区几何对象
*/
_updateByStage(stage) {
this._drawTool.sketchStage.getAllFeatures().forEach((feature) => {
this._drawTool._removeFeatureFromMap(feature)
})
this._drawTool._setSketchStage(stage)
// this.view.sketchFeatures = [] // 临时
const drawTools = []
stage.getAllFeatures().forEach((feature) => {
const drawTool = this._updateDrawToolByFeature(feature)
if (drawTool) {
drawTools.push(drawTool)
drawTool._addFeaturesToMap(feature)
}
})
// this._addFeatureToSketchEditor(stage.entityGraphic, true)
// if (stage.editMode === 1) {
// this._drawTool.selectFeature(stage.entityGraphic)
// } else if (stage.editMode === 2) {
// this._drawTool.selectFeatureVertex(stage.entityGraphic)
// }
}
/**
* 根据sketchStage状态更新草图
* @private
* @param {SketchStage} stage 被合并的区几何对象
*/
getDrawToolByFeatureId(id) {
let drawTool = null
for (let i = 0; i < this._drawTool._drawTools.length; i++) {
if (this._drawTool._drawTools[i].sketchStage.entityGraphic.id === id) {
drawTool = this._drawTool._drawTools[i]
break
}
}
return drawTool
}
/**
* 根据feature图形更新所属草图绘制工具drawTool
* @private
* @param {Feature} feature
*/
_updateDrawToolByFeature(feature) {
let drawTool = null
const drawTools = this._drawTool._drawTools
for (let i = 0; i < drawTools.length; i++) {
if (drawTools[i].sketchStage.entityGraphic.id === feature.id) {
drawTools[i].sketchStage.entityGraphic = feature
drawTool = drawTools[i]
break
}
}
return drawTool
}
/**
* 响应草图被选中事件
* @private
*/
_selectEvent() {
this._drawTool.on('selected', (event) => {
const selectedSketch = event.isSelected ? this : null
this.fire(
'selected',
{ selectedSketch, isSelected: event.isSelected },
this
)
})
}
/**
* 响应草图绘制完毕事件
* @private
*/
_drawEvent() {
this._drawTool.on('drawn', (event) => {
this.fire('drawn', { geometry: event.geometry }, this)
this._drawTool.state = 'drawn'
this._bindHit(this._drawTool)
})
this._drawTool.on('drawn-vertex', (event) => {
this.fire('drawn-vertex', { geometry: event.geometry }, this)
})
}
/**
* 获取草图几何对象<a id='getGeometry'></a>
* @returns {Geometry}
*/
getGeometry() {
return this.sketchStage.entityGraphic.geometry
}
/**
* 设置捕捉配置项
* @param {Boolean} [snapOption.isSnapVertexCoincident = false] 是否自动捕捉顶点重合
* @param {Boolean} [snapOption.isSnapVertexInLine = false] 是否自动捕捉线上的点
* @param {Boolean} [snapOption.isSnapPerpendicular = false] 是否自动捕捉垂线垂点
* @param {Boolean} [snapOption.isSnapParallel = false] 是否自动捕捉平行线
* @param {Boolean} [snapOption.snapSketchGeometry = false] 是否捕捉正在绘制的图形的边界
*/
setSnapOption(snapOption) {
this.snapOption = Object.assign(this.snapOption, snapOption)
this._sketchSnappingTool.setSnapOption(snapOption)
}
/**
* 获取捕捉配置项
* @returns {Object} 捕捉配置项
*/
getSnapOption() {
return this.snapOption
}
/**
* 设置捕获和线造区边界参考几何图形集合
* @param {Array<Geometry>} geometries 几何图形集合
*/
setSnapAndReferGeometries(geometries) {
this.snapAndReferGeometries = geometries
}
/**
* 获取捕捉配置项
* @returns {Array<Geometry>} 捕捉配置项
*/
getSnapAndReferGeometries() {
return this.snapAndReferGeometries
}
_bindHit() {}
}
Zondy.SketchEditor = SketchEditor
export default SketchEditor