@geogirafe/lib-geoportal
Version:
GeoGirafe is a flexible application to build online geoportals.
331 lines (330 loc) • 11.6 kB
JavaScript
// 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;
}
}
}
}