UNPKG

@geogirafe/lib-geoportal

Version:

GeoGirafe is a flexible application to build online geoportals.

331 lines (330 loc) 11.6 kB
// SPDX-License-Identifier: Apache-2.0 import { v4 as uuidv4 } from 'uuid'; import { Fill, RegularShape, Stroke, Style } from 'ol/style.js'; import { toRadians } from 'ol/math.js'; import { Circle as CircleGeom } from 'ol/geom.js'; import Feature from 'ol/Feature.js'; import GeoJSON from 'ol/format/GeoJSON.js'; import { getAreaAsMetricText, getAzimuthAsText, getLengthAsMetricText } from '../utils/olutils.js'; import { fromCircle } from 'ol/geom/Polygon.js'; export var DrawingShape; (function (DrawingShape) { DrawingShape[DrawingShape["Point"] = 0] = "Point"; DrawingShape[DrawingShape["Polyline"] = 1] = "Polyline"; DrawingShape[DrawingShape["Polygon"] = 2] = "Polygon"; DrawingShape[DrawingShape["Square"] = 3] = "Square"; DrawingShape[DrawingShape["Rectangle"] = 4] = "Rectangle"; DrawingShape[DrawingShape["Disk"] = 5] = "Disk"; DrawingShape[DrawingShape["FreehandPolyline"] = 6] = "FreehandPolyline"; DrawingShape[DrawingShape["FreehandPolygon"] = 7] = "FreehandPolygon"; })(DrawingShape || (DrawingShape = {})); export default class DrawingFeature { _tool; _name; _nameColor; _strokeColor; _strokeWidth; _fillColor; _lineStroke = 'full'; _arrowStyle = 'none'; _arrowPosition = 'whole'; _measureInformation = 'simple'; _nameFontSize; _measureFontSize; _measureColor; _font; _displayName = true; _selected = false; geojson = {}; id = uuidv4(); onChange = () => { }; drawingState; defaultDrawingConfig; constructor(tool, drawingState, drawingConfig) { this.defaultDrawingConfig = drawingConfig; this.drawingState = drawingState; this._tool = tool; this._name = this.getDefaultName(DrawingShape[tool]); this._strokeColor = this.defaultDrawingConfig.defaultStrokeColor; this._strokeWidth = this.defaultDrawingConfig.defaultStrokeWidth; this._fillColor = this.defaultDrawingConfig.defaultFillColor; this._nameFontSize = this.defaultDrawingConfig.defaultTextSize; this._measureFontSize = this.defaultDrawingConfig.defaultTextSize; this._font = this.defaultDrawingConfig.defaultFont; this._nameColor = '#000000'; this._measureColor = '#000000'; } get name() { return this._name; } set name(v) { this._name = v; this.onChange(this); } get strokeColor() { return this._strokeColor; } set strokeColor(v) { this._strokeColor = v; this.onChange(this); } get strokeWidth() { return this._strokeWidth; } set strokeWidth(v) { this._strokeWidth = v; this.onChange(this); } get lineStroke() { return this._lineStroke; } set lineStroke(v) { this._lineStroke = v; this.onChange(this); } get fillColor() { return this._fillColor; } set fillColor(v) { this._fillColor = v; this.onChange(this); } get arrowStyle() { return this._arrowStyle; } set arrowStyle(v) { this._arrowStyle = v; this.onChange(this); } get arrowPosition() { return this._arrowPosition; } set arrowPosition(v) { this._arrowPosition = v; this.onChange(this); } get nameFontSize() { return this._nameFontSize; } set nameFontSize(v) { this._nameFontSize = v; this.onChange(this); } get measureFontSize() { return this._measureFontSize; } set measureFontSize(v) { this._measureFontSize = v; this.onChange(this); } get font() { return this._font; } set font(v) { this._font = v; this.onChange(this); } get displayName() { return this._displayName; } set displayName(v) { this._displayName = v; this.onChange(this); } get measureInformation() { return this._measureInformation; } set measureInformation(v) { this._measureInformation = v; this.onChange(this); } get nameColor() { return this._nameColor; } set nameColor(v) { this._nameColor = v; this.onChange(this); } get measureColor() { return this._measureColor; } set measureColor(v) { this._measureColor = v; this.onChange(this); } get type() { return this._tool; } get selected() { return this._selected; } set selected(v) { this._selected = v; this.onChange(this); } addToState() { this.drawingState.features.push(this); } serialize() { return { n: this._name, sc: this._strokeColor, sw: this._strokeWidth, ls: this._lineStroke, as: this._arrowStyle, ap: this._arrowPosition, fc: this._fillColor, nfz: this._nameFontSize, mfz: this._measureFontSize, f: this._font, g: this.geojson, t: this._tool, dn: this._displayName, mi: this._measureInformation, nc: this._nameColor, mc: this._measureColor, s: this._selected }; } getLengthText(length) { return getLengthAsMetricText(this.measureInformation === 'none' ? undefined : length); } getAreaText(area) { return getAreaAsMetricText(this.measureInformation === 'none' ? undefined : area); } getCoordText(coord) { if (this.measureInformation === 'none') { return ''; } if (coord.length > 2) { return 'E ' + coord[0].toFixed(2) + '\nN ' + coord[1].toFixed(2) + '\nH ' + coord[2].toFixed(2); } return 'E ' + coord[0].toFixed(2) + '\nN ' + coord[1].toFixed(2); } getAzimuthText(circle) { return getAzimuthAsText(this.measureInformation === 'none' ? undefined : (circle.getProperties()['azimuth'] ?? undefined)); } isPointOrPolyline() { return (this.type === DrawingShape.Point || this.type === DrawingShape.Polyline || this.type === DrawingShape.FreehandPolyline); } getVertexStyle(activeNode = false) { const defaultConfig = this.defaultDrawingConfig; return new Style({ zIndex: 1002, image: new RegularShape({ points: 4, radius: activeNode ? defaultConfig.defaultVertexRadius + 2 : defaultConfig.defaultVertexRadius, angle: toRadians(45), rotateWithView: false, fill: new Fill({ color: defaultConfig.defaultVertexFillColor }), stroke: new Stroke({ color: this.strokeColor, width: activeNode ? defaultConfig.defaultVertexStrokeWidth + 2 : defaultConfig.defaultVertexStrokeWidth }) }) }); } static deserialize(serializedFeature, context, drawingState) { const newFeature = new DrawingFeature(serializedFeature.t, drawingState, context.configManager.Config.drawing); newFeature.geojson = serializedFeature.g; newFeature.name = serializedFeature.n; newFeature.strokeColor = serializedFeature.sc; newFeature.strokeWidth = serializedFeature.sw; newFeature.lineStroke = serializedFeature.ls; newFeature.arrowStyle = serializedFeature.as; newFeature.arrowPosition = serializedFeature.ap; newFeature.fillColor = serializedFeature.fc; newFeature.nameFontSize = serializedFeature.nfz; newFeature.measureFontSize = serializedFeature.mfz; newFeature.font = serializedFeature.f; newFeature.displayName = serializedFeature.dn; newFeature.measureInformation = serializedFeature.mi; newFeature.nameColor = serializedFeature.nc; newFeature.measureColor = serializedFeature.mc; newFeature.selected = serializedFeature.s; newFeature.addToState(); return newFeature; } static circleToPolygon(center, radius, nbEdges = 300) { const positions = []; for (let i = 0; i < 2 * Math.PI; i += (2 * Math.PI) / nbEdges) { positions.push([center[0] + radius * Math.cos(i), center[1] + radius * Math.sin(i)]); } return [...positions, positions[0]]; } static geojsonFromOlFeature(olFeature, shapeType) { if (shapeType === DrawingShape.Disk) { const disk = olFeature.getGeometry(); const center = disk.getCenter(); const radius = disk.getRadius(); const pointer = disk.getProperties()['pointer'] ?? [center[0] + radius, center[1]]; return { type: 'Feature', geometry: { type: 'Disk', center: center, radius: radius, azimuth: disk.getProperties()['azimuth'] ?? 0, pointer: pointer } }; } return JSON.parse(new GeoJSON().writeFeature(olFeature)); } /** * Create an ol feature instance from a geojson object, optionally simplifying circle geometries to polygons * with a limited number of edges to improve support across formats and services. */ static olFeatureFromGeoJson(geoJson, circleToPolygonNbEdges) { const geometry = geoJson.geometry; // Handle custom "Disk" geometry type if (geometry?.type === 'Disk') { if (!(Array.isArray(geometry.center) && geometry.center.length >= 2 && typeof geometry.radius === 'number')) { throw new Error('Invalid Disk geometry format'); } let olGeometry = new CircleGeom(geometry.center, geometry.radius); if (circleToPolygonNbEdges) { olGeometry = fromCircle(olGeometry, circleToPolygonNbEdges); } olGeometry.setProperties({ azimuth: geometry.azimuth, pointer: geometry.pointer }); return new Feature(olGeometry); } else { // Standard case: parse with GeoJSON format reader const feature = new Feature(new GeoJSON().readFeatures(geoJson)[0].getGeometry()); if (Array.isArray(feature)) { throw new TypeError('GeoJSON feature is an array, expected single feature'); } return feature; } } /** * Finds a name composed of the shape type and a number. * The number starts from how many shapes of the same type are already in the state +1. * Then checks if such a candidate name already exists and increments if so. * @param geometryTypename * @returns */ getDefaultName(geometryTypename) { const shapeTypeId = DrawingShape[geometryTypename]; const drawingFeaturesSameType = this.drawingState.features.filter((feat) => feat.type === shapeTypeId); const existingNames = drawingFeaturesSameType.map((f) => f.name.toLowerCase().trim()); let counter = existingNames.length + 1; while (true) { const nameCandidate = `${geometryTypename} ${counter}`; if (existingNames.includes(nameCandidate.toLowerCase())) { counter++; } else { return nameCandidate; } } } }