import { unByKey } from 'ol/Observable'
import VectorLayer from 'ol/layer/Vector'
import VectorSource from 'ol/source/Vector'
import Feature from 'ol/Feature'
import { Fill, Stroke, Style } from 'ol/style'
import GeoJSON from 'ol/format/GeoJSON'
import ControlCreateTools from '../../control/ControlCreateTools'
import JSTSGeoJSON from 'jsts/org/locationtech/jts/io/GeoJSONParser'
import UnionOp from 'jsts/org/locationtech/jts/operation/union/UnionOp'
import Polygonizer from 'jsts/org/locationtech/jts/operation/polygonize/Polygonizer'
import GeometryFactory from 'jsts/org/locationtech/jts/geom/GeometryFactory'
import Coordinate from 'jsts/org/locationtech/jts/geom/Coordinate'
/**
* Est-ce que cette feature peut-être découpée?
* @param {Feature} feature
* @returns est-ce que la feature peut être découpée
*/
export function isSplitPossible (feature) {
return feature &&
(feature.getGeometry().getType() === 'Polygon' ||
(feature.getGeometry().getType() === 'MultiPolygon' && feature.getGeometry().getPolygons().length === 1))
}
/**
* Lance le découpage de la feature
* @param {Object} options
* @param {Feature} options.feature
* @param {Viewer} options.viewer instance active du viewer
* @param {string} [options.toolId] Outil qui demande la découpe
* @param {object} [options.snapOptions] options de snap (doc TODO)
* @returns les features crées, null si aucune n'a été créé
*/
export async function splitFeature (options = {}) {
const { feature, viewer, toolId } = options
let { snapOptions = {} } = options
const rtn = new Promise((resolve, reject) => {
if (!isSplitPossible(feature)) {
console.warn('Feature incorrecte en entrée', feature)
return
}
snapOptions = {
snapGroups: ['KMAPV'].concat(snapOptions?.snapGroups || []),
pixelTolerance: snapOptions?.pixelTolerance || 20,
}
// #region style d'affichage
const white = [255, 255, 255, 1]
const sketchColor = [0, 153, 255, 1]
const sketchFillColor = [0, 153, 255, 0.5]
const validColor = [40, 180, 40, 0.5]
const width = 3
// le polygone d'origine
const editStyle = [
// polygone
new Style({
fill: new Fill({
color: sketchFillColor,
}),
}),
// lignes
new Style({
stroke: new Stroke({
color: white,
width: width + 2,
}),
}),
new Style({
stroke: new Stroke({
color: sketchColor,
width,
lineDash: [10, 5],
}),
}),
]
// l'affichage "valide"
const validStyle = [
// polygone
new Style({
fill: new Fill({
color: validColor,
}),
}),
// lignes
new Style({
stroke: new Stroke({
color: white,
width: width + 2,
}),
}),
new Style({
stroke: new Stroke({
color: validColor,
width,
lineDash: [10, 5],
}),
}),
]
// #endregion
const geojsonReader = new GeoJSON()
const jstsParser = new JSTSGeoJSON()
// Create a GeometryFactory if you don't have one already
const geometryFactory = new GeometryFactory()
/** on se s'interesse qu'a la géométrie, on utilise une copie de feature */
const originalFeature = new Feature({ geometry: feature.getGeometry() })
const polygonAsGeoJson = geojsonReader.writeGeometryObject(
originalFeature.getGeometry().getType() === 'Polygon'
? originalFeature.getGeometry()
: originalFeature.getGeometry().getPolygons()[0])
// lit la géométrie ol vers jsts
const originPolygon = jstsParser.read(polygonAsGeoJson)
// calque temporaire des modifications
const modifyFeatureLayer = new VectorLayer({
[viewer.commonLayer.propertiesName.ID_LAYER]: '__SPLIT_FEATURE_LAYER',
// zIndex: 10000,
source: new VectorSource(),
style: editStyle,
displayInLayerSwitcher: false,
})
// variables pour faire le ménage
const featureWasVisible = feature.get(viewer.dataLayer.propertiesName.VISIBLE_FEATURE) !== false
const horizontalToolbarVisility = viewer.horizontalToolbar.getVisible()
const currentMapMode = viewer.commonLayer.getCurrentMapMode()
const events = []
// fait le ménage
function removeInterraction () {
if (featureWasVisible) { viewer.dataLayer.showFeature(feature) }
// retire les ecouteurs d'évènements nécessaire au control
events.forEach(unByKey)
viewer.Map.removeControl(createTool)
// drawInteraction.setActive(false)
// viewer.Map.removeInteraction(drawInteraction)
viewer.getFeatureSnapper().removeSource(modifyFeatureLayer.getSource())
viewer.commonLayer.removeLayer('__SPLIT_FEATURE_LAYER', 'SYSTEM')
viewer.horizontalToolbar.setVisible(horizontalToolbarVisility)
viewer.dataLayer.changeMapMode(currentMapMode.name, currentMapMode.toolId)
}
viewer.dataLayer.changeMapMode('_drawLineStringTouch', toolId)
const mapMode = viewer.commonLayer.getCurrentMapMode()
const [drawInteraction] = mapMode.interactions
// la boite d'outils de création ne fait qu'utiliser l'interaction drawtouch venant de datalayer, on fourni la bonne
const createToolOptions = {
viewer,
geometryType: 'LineString',
interaction: drawInteraction,
title: 'Tracer la ligne de coupe',
digitalizeOptions: {
snapOptions,
},
}
const createTool = new ControlCreateTools(createToolOptions)
function getExtendedPoint (coordinate1, coordinate2) {
const ax = coordinate1[0]
const ay = coordinate1[1]
const bx = coordinate2[0]
const by = coordinate2[1]
const len = 0.5
const lengthAB = Math.sqrt(Math.pow((ax - bx), 2) + Math.pow((ay - by), 2))
const cx = bx + (bx - ax) / lengthAB * len
const cy = by + (by - ay) / lengthAB * len
return !isNaN(cx) && !isNaN(cy) ? [cx, cy] : null
}
// effectu la découpe a l'aide de jsts
function cutPolygonWithLineCoordinates (coordinates) {
coordinates = [].concat(coordinates)
// on prolonge les coordonnées de quelques cm afin de dépasser du polygone
const prolongementDebut = getExtendedPoint(coordinates[1], coordinates[0])
const prolongementFin = getExtendedPoint(coordinates[coordinates.length - 2], coordinates[coordinates.length - 1])
if (prolongementDebut) {
coordinates.splice(0, 0, prolongementDebut)
}
if (prolongementFin) {
coordinates.push(prolongementFin)
}
// Parse Line geometry to jsts type
const line = geometryFactory.createLineString(coordinates.map(coordinates => new Coordinate(coordinates[0], coordinates[1])))
// Perform union of Polygon and Line and use Polygonizer to split the polygon by line
const union = UnionOp.union(originPolygon.getExteriorRing(), line)
const polygonizer = new Polygonizer()
// Splitting polygon in two part
polygonizer.add(union)
// Get splitted polygons (transforme les lignes en polygones)
return polygonizer.getPolygons()
}
function onGeomChange (coordinates) {
const polygons = cutPolygonWithLineCoordinates(coordinates)
// This will execute only if polygon is successfully splitted into two parts
if (polygons.array.length === 2) {
// Clear old splitted polygons and measurement ovelays
modifyFeatureLayer.getSource().clear()
polygons.array.forEach((geom) => {
const jsonSplittedPolygon = jstsParser.write(geom)
const feature = geojsonReader.readFeature(jsonSplittedPolygon)
// Add splitted polygon to vector layer
modifyFeatureLayer.getSource().addFeature(feature)
// Change Style of Splitted polygons
modifyFeatureLayer.setStyle(validStyle)
})
} else if (polygons.array.length < 2) {
// si on a retiré des points et qu'on a plus de découpe on revient au status de départ
modifyFeatureLayer.setStyle(editStyle)
modifyFeatureLayer.getSource().clear()
// Add original polygon to vector layer if no intersection is there between line and polygon
modifyFeatureLayer.getSource().addFeature(originalFeature)
}
}
// on va d'abord ajouter un calque temporaire puis copier la feature dessus
try {
viewer.dataLayer.hideFeature(feature)
// #region Calque et interraction nécessaire a cet outil
viewer.commonLayer.addLayer(modifyFeatureLayer, 'SYSTEM')
viewer.getFeatureSnapper().addSource({
groups: [{ name: 'KMAPV', edge: true, vertex: true }],
source: modifyFeatureLayer.getSource(),
})
modifyFeatureLayer.getSource().addFeature(originalFeature)
viewer.horizontalToolbar.setVisible(false)
// viewer.Map.addInteraction(drawInteraction)
viewer.Map.addControl(createTool)
let modifyFeatureEvent = null
events.push(drawInteraction.on('drawabort', () => {
// on remet en mapmode normal
unByKey(modifyFeatureEvent)
removeInterraction.call(this)
resolve(null)
}))
events.push(drawInteraction.on('drawstart', (e) => {
modifyFeatureEvent = e.feature.on('change', (e) => {
if (e.target?.getGeometry()?.getCoordinates()?.length > 1) {
onGeomChange.call(this, e.target.getGeometry().getCoordinates())
}
})
}))
events.push(drawInteraction.on('drawend', (e) => {
// lorsque l'utilisateur termine, si on a deux polygone on
const createdFeatures = modifyFeatureLayer.getSource().getFeatures()
unByKey(modifyFeatureEvent)
removeInterraction.call(this)
resolve(createdFeatures.length === 2 ? createdFeatures : null)
}))
// resolve(evt)
// #endregion
} catch (err) {
console.error('[splitFeature]', err)
removeInterraction.call(this)
reject(err)
}
})
return rtn
}
export const FeatureSplit = {
isSplitPossible,
}