UNPKG

@geogirafe/lib-geoportal

Version:

GeoGirafe is a flexible application to build online geoportals.

383 lines (382 loc) 14 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'; import LineString from 'ol/geom/LineString.js'; // New shapes must be appended at the end, as the numeric values are used to serialize features. 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["Text"] = 8] = "Text"; })(DrawingShape || (DrawingShape = {})); export default class DrawingFeature { static geoJSONFormat = new GeoJSON(); _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)); } isPointPolylineOrText() { return (this.type === DrawingShape.Point || this.type === DrawingShape.Polyline || this.type === DrawingShape.FreehandPolyline || this.type === DrawingShape.Text); } 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 }) }) }); } /** * Creates a new DrawingFeature from an ol feature */ static createFromOlFeature(shape, olFeature, context, drawingState) { const newFeature = new DrawingFeature(shape, drawingState, context.configManager.Config.drawing); newFeature.geojson = DrawingFeature.geojsonFromOlFeature(olFeature, shape); newFeature.addToState(); return newFeature; } 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 { id: olFeature.getId(), type: 'Feature', geometry: { type: 'Disk', center: center, radius: radius, azimuth: disk.get('azimuth') ?? 0, pointer: pointer } }; } return JSON.parse(DrawingFeature.geoJSONFormat.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 }); const feature = new Feature(olGeometry); if (geoJson.id) { feature.setId(geoJson.id); } return feature; } else { // Standard case: parse with GeoJSON format reader const feature = new Feature(DrawingFeature.geoJSONFormat.readFeatures(geoJson)[0].getGeometry()); if (Array.isArray(feature)) { throw new TypeError('GeoJSON feature is an array, expected single feature'); } return feature; } } /** * Transform a geojson (valid object, or disk with its properties) to a new projection. * @returns The transformed geojson object or null. */ static transformGeoJson(geoJson, oldProj, newProj) { if (oldProj === null || oldProj === newProj) { return geoJson; } try { if (geoJson.geometry?.type === 'Disk') { // Case of disks that are not a valid geojson object, handle them manually. const olFeature = DrawingFeature.olFeatureFromGeoJson(geoJson); const geom = olFeature.getGeometry(); geom?.transform(oldProj, newProj); if (geom) { const center = geom.getCenter(); const line = new LineString([center, [center[0], center[1] + geom.getRadius()]]); line.rotate(toRadians((geom.get('azimuth') ?? 0) * -1), center); geom.set('pointer', line.getLastCoordinate()); } return DrawingFeature.geojsonFromOlFeature(olFeature, DrawingShape.Disk); } // Case of valid geojson objects. return DrawingFeature.geoJSONFormat.writeFeatureObject(DrawingFeature.geoJSONFormat.readFeature(geoJson, { dataProjection: oldProj, featureProjection: newProj })); } catch (e) { console.error('Error transforming this draw object to the new projection.', e); } return null; } /** * 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; } } } }