UNPKG

maptalks.multisuite

Version:

An Suite to Combine <-> Decompose, Peel <-> Fill MultiPolygon.

481 lines (436 loc) 15.7 kB
import isEqual from 'lodash.isequal' const uid = 'multisuite@cXiaof' const options = { colorHit: '#ffa400', colorChosen: '#00bcd4', } const markerStyleDefault = { markerType: 'path', markerPath: [ { path: 'M8 23l0 0 0 0 0 0 0 0 0 0c-4,-5 -8,-10 -8,-14 0,-5 4,-9 8,-9l0 0 0 0c4,0 8,4 8,9 0,4 -4,9 -8,14z M3,9 a5,5 0,1,0,0,-0.9Z', fill: '#DE3333', }, ], markerPathWidth: 16, markerPathHeight: 23, markerWidth: 24, markerHeight: 34, } export class MultiSuite extends maptalks.Class { constructor(options) { super(options) this._layerName = `${maptalks.INTERNAL_LAYER_PREFIX}${uid}` this._layerTMP = `${maptalks.INTERNAL_LAYER_PREFIX}${uid}_temp` this._chooseGeos = [] } combine(geometry, targets) { if (geometry instanceof maptalks.Geometry) { this._initialTaskWithGeo(geometry, 'combine') if (targets instanceof maptalks.Geometry) targets = [targets] if (targets instanceof Array && targets.length > 0) { this._compositWithTargets(targets) this.remove() } else this._initialChooseGeos(geometry) } } decompose(geometry, targets) { if (geometry instanceof maptalks.GeometryCollection) { this._initialTaskWithGeo(geometry, 'decompose') if (targets instanceof maptalks.Geometry) targets = [targets] if (targets instanceof Array && targets.length > 0) { this._decomposeWithTargets(targets) this.remove() } else this._initialChooseGeos(geometry) } } peel(geometry, targets) { if (geometry instanceof maptalks.Polygon) { this._initialTaskWithGeo(geometry, 'peel') if (targets instanceof maptalks.Polygon) targets = [targets] if (targets instanceof Array && targets.length > 0) { this._peelWithTargets(targets) this.remove() } } } fill(geometry, targets, fillAll) { if (geometry instanceof maptalks.MultiPolygon) { this._initialTaskWithGeo(geometry, 'fill') if (fillAll) { this._fillAll() this.remove() } else { if (targets instanceof maptalks.Polygon) targets = [targets] if (targets instanceof Array && targets.length > 0) { this._fillWithTargets(targets) this.remove() } else { const coords0 = this.geometry.getCoordinates()[0] const symbol = this.geometry.getSymbol() symbol.polygonOpacity = 0 coords0.forEach((coord, index) => { if (index > 0) new maptalks.Polygon([coords0[index]], { symbol, }).addTo(this._tmpLayer) }) } } } } submit(callback = () => false) { switch (this._task) { case 'combine': this._submitCombine() break case 'decompose': this._submitDecompose() break case 'peel': this._submitPeel() break case 'fill': this._submitFill() break default: break } callback(this._result, this._deals, this._task) this.remove() } cancel() { this.remove() } remove() { if (this._tmpLayer) this._tmpLayer.remove() if (this._chooseLayer) this._chooseLayer.remove() this._chooseGeos = [] this._offMapEvents() delete this._result delete this._deals delete this._task delete this._tmpLayer delete this._chooseLayer } _initialTaskWithGeo(geometry, task) { if (this.geometry) this.remove() this._task = task this._savePrivateGeometry(geometry) } _savePrivateGeometry(geometry) { this.geometry = geometry this.layer = geometry.getLayer() this._addTo(geometry.getMap()) } _addTo(map) { if (this._chooseLayer) this.remove() if (map._map_tool && map._map_tool instanceof maptalks.DrawTool) map._map_tool.disable() this._map = map this._tmpLayer = this._newVectorLayerToFront(this._layerTMP) this._chooseLayer = this._newVectorLayerToFront(this._layerName) this._registerMapEvents() } _newVectorLayerToFront(name) { return new maptalks.VectorLayer(name).addTo(this._map).bringToFront() } _registerMapEvents() { map.on('mousemove', this._mousemoveEvents, this) map.on('click', this._clickEvents, this) } _offMapEvents() { map.off('mousemove', this._mousemoveEvents, this) map.off('click', this._clickEvents, this) } _mousemoveEvents(e) { let geos = [] switch (this._task) { case 'combine': geos = this.layer.identify(e.coordinate) break case 'decompose': geos = this._tmpLayer.identify(e.coordinate) break case 'peel': const coordPeel = this._getSafeCoords() this.layer.identify(e.coordinate).forEach((geo) => { const coord = this._getSafeCoords(geo) if (!isEqual(coord, coordPeel)) geos.push(geo) }) break case 'fill': geos = this._tmpLayer.identify(e.coordinate) break default: break } this._updateHitGeo(geos) } _getSafeCoords(geo = this.geometry) { let coords = geo.getCoordinates() if (geo.options.numberOfShellPoints) { const { options } = geo const { numberOfShellPoints } = options options.numberOfShellPoints = 300 geo.setOptions(options) coords = [geo.getShell()] options.numberOfShellPoints = numberOfShellPoints || 60 geo.setOptions(options) } return coords } _updateHitGeo(geos) { const id = '_hit' if (this._needRefreshSymbol) { const hitGeoCopy = this._chooseLayer.getGeometryById(id) if (hitGeoCopy) { hitGeoCopy.remove() delete this.hitGeo } this._needRefreshSymbol = false } if (geos && geos.length > 0) { this._needRefreshSymbol = true this.hitGeo = geos[0] if (this._checkIsSameType(this.hitGeo)) { const hitSymbol = this._getSymbolOrDefault(this.hitGeo, 'Hit') this._copyGeoUpdateSymbol(this.hitGeo, hitSymbol).setId(id) } else delete this.hitGeo } } _checkIsSameType(geo) { const typeHit = geo.getType() const typeThis = this.geometry.getType() return typeHit.includes(typeThis) || typeThis.includes(typeHit) } _getSymbolOrDefault(geo, type) { let symbol = geo.getSymbol() const color = this.options[`color${type}`] const lineWidth = 4 if (symbol) { for (let key in symbol) { if (key.endsWith('Fill') || key.endsWith('Color')) symbol[key] = color } symbol.lineWidth = lineWidth } else { if (geo.getType().endsWith('Point')) symbol = Object.assign(markerStyleDefault, { markerFill: color, }) else symbol = { lineColor: color, lineWidth } } return symbol } _copyGeoUpdateSymbol(geo, symbol) { return geo.copy().updateSymbol(symbol).addTo(this._chooseLayer) } _clickEvents(e) { switch (this._task) { case 'combine': this._clickCombine() break case 'decompose': this._clickDecompose(e) break default: if (this.hitGeo) { const coordHit = this._getSafeCoords(this.hitGeo) this._setChooseGeosExceptHit(coordHit) this._updateChooseGeos() } break } } _clickCombine() { if (this.hitGeo) { const coordHit = this._getSafeCoords(this.hitGeo) const coordThis = this._getSafeCoords() if (isEqual(coordHit, coordThis)) return null this._setChooseGeosExceptHit(coordHit) this._updateChooseGeos() } } _setChooseGeosExceptHit(coordHit) { const chooseNext = this._chooseGeos.reduce((target, geo) => { const coord = this._getSafeCoords(geo) if (isEqual(coordHit, coord)) return target return [...target, geo] }, []) if (chooseNext.length === this._chooseGeos.length) this._chooseGeos.push(this.hitGeo) else this._chooseGeos = chooseNext } _updateChooseGeos() { const layer = this._chooseLayer layer.clear() this._chooseGeos.forEach((geo) => { const chooseSymbol = this._getSymbolOrDefault(geo, 'Chosen') this._copyGeoUpdateSymbol(geo, chooseSymbol) }) } _clickDecompose(e) { const geosAll = this._chooseLayer.identify(e.coordinate) const geos = geosAll.reduce((target, geo) => { if (geo.getId() !== '_hit') target.push(geo) return target }, []) if (geos.length > 0) { const geo = geos[0] const coordHit = this._getSafeCoords(geo) this._setChooseGeosExceptHit(coordHit, true) geo.remove() } else if (this.hitGeo) this._chooseGeos.push(this.hitGeo) this._updateChooseGeos() } _initialChooseGeos(geometry) { switch (this._task) { case 'combine': this._chooseGeos = [geometry] break case 'decompose': geometry.forEach((geo) => geo.copy().addTo(this._tmpLayer)) this._chooseGeos = this._tmpLayer.getGeometries() break default: break } this._updateChooseGeos() } _submitCombine() { this._compositWithTargets() } _compositWithTargets(targets = this._chooseGeos) { this._deals = targets.map((geo) => geo.copy()) const geosCoords = this._getGeoStringifyCoords(targets) const geometries = this.layer.getGeometries() const geos = geometries.reduce((target, geo) => { const coord = this._getGeoStringifyCoords(geo) if (geosCoords.includes(coord)) { if (geo.getType().startsWith('Multi')) geo.forEach((item) => target.push(item.copy())) else target.push(geo.copy()) geo.remove() } return target }, []) this._compositResultGeo(geos) } _getGeoStringifyCoords(geo) { if (geo instanceof Array) return geo.map((item) => JSON.stringify(this._getSafeCoords(item))) return JSON.stringify(this._getSafeCoords(geo)) } _compositResultGeo(geos) { const { length } = geos if (!length || length === 0) return null let combine if (length > 1) switch (geos[0].getType()) { case 'Point': combine = new maptalks.MultiPoint(geos) break case 'LineString': combine = new maptalks.MultiLineString(geos) break default: combine = new maptalks.MultiPolygon(geos) break } else combine = geos[0].copy() combine.setSymbol(this.geometry.getSymbol()) combine.setProperties(this.geometry.getProperties()) combine.addTo(this.layer) this._result = combine } _submitDecompose() { this._decomposeWithTargets() } _decomposeWithTargets(targets = this._chooseLayer.getGeometries()) { const geosCoords = targets.reduce((target, geo) => { if (geo.getId() !== '_hit') target.push(this._getGeoStringifyCoords(geo)) return target }, []) const geosTmp = this._tmpLayer.getGeometries() let geos = [] this._deals = geosTmp.reduce((target, geo) => { const coord = this._getGeoStringifyCoords(geo) if (geosCoords.includes(coord)) geos.push(geo.copy()) else { geo = geo.copy().addTo(this.layer) target.push(geo) } return target }, []) this.geometry.remove() this._compositResultGeo(geos) } _peelWithTargets(targets = this._chooseGeos) { const geometry = this.geometry if (targets.length > 0) { this._deals = [] const arr = targets.reduce( (target, geo) => { if (geo instanceof maptalks.MultiPolygon) geo.forEach((item) => target.push(this._getSafeCoords(item)[0]) ) else target.push(this._getSafeCoords(geo)[0]) this._deals.push(geo.copy()) geo.remove() return target }, [this._getSafeCoords(geometry)[0]] ) this._result = new maptalks.MultiPolygon([arr], { symbol: geometry.getSymbol(), properties: geometry.getProperties(), }).addTo(this.layer) } else this._result = geometry.copy().addTo(this.layer) geometry.remove() } _submitPeel() { this._peelWithTargets() } _submitFill() { this._fillWithTargets() } _fillAll() { const coords = this.geometry.getCoordinates() const symbol = this.geometry.getSymbol() const properties = this.geometry.getProperties() const result = new maptalks.Polygon([coords[0][0]], { symbol, properties, }).addTo(this.layer) this.geometry.remove() return result } _fillWithTargets(targets = this._chooseGeos) { const symbol = this.geometry.getSymbol() const properties = this.geometry.getProperties() let coordsStr = [] this._deals = targets.map((target) => { const coordsTarget = JSON.stringify(target.getCoordinates()[0]) coordsStr.push(coordsTarget) return target.copy().setSymbol(symbol) }) const firstGeo = this.geometry.getCoordinates()[0] const coords = firstGeo.reduce((target, coord) => { if (!coordsStr.includes(JSON.stringify(coord))) target.push(coord) return target }, []) if (coords.length === 1) this._result = this._fillAll() else { this._result = new maptalks.MultiPolygon([coords], { symbol, properties, }).addTo(this.layer) this.geometry.remove() } } } MultiSuite.mergeOptions(options)