/** * @module ol/geom/SimpleGeometry */ import Geometry from './Geometry.js'; import {abstract} from '../util.js'; import {createOrUpdateFromFlatCoordinates, getCenter} from '../extent.js'; import {rotate, scale, transform2D, translate} from './flat/transform.js'; /** * @classdesc * Abstract base class; only used for creating subclasses; do not instantiate * in apps, as cannot be rendered. * * @abstract * @api */ class SimpleGeometry extends Geometry { constructor() { super(); /** * @protected * @type {import("./Geometry.js").GeometryLayout} */ this.layout = 'XY'; /** * @protected * @type {number} */ this.stride = 2; /** * @protected * @type {Array} */ this.flatCoordinates = null; } /** * @param {import("../extent.js").Extent} extent Extent. * @protected * @return {import("../extent.js").Extent} extent Extent. */ computeExtent(extent) { return createOrUpdateFromFlatCoordinates( this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, extent ); } /** * @abstract * @return {Array<*> | null} Coordinates. */ getCoordinates() { return abstract(); } /** * Return the first coordinate of the geometry. * @return {import("../coordinate.js").Coordinate} First coordinate. * @api */ getFirstCoordinate() { return this.flatCoordinates.slice(0, this.stride); } /** * @return {Array} Flat coordinates. */ getFlatCoordinates() { return this.flatCoordinates; } /** * Return the last coordinate of the geometry. * @return {import("../coordinate.js").Coordinate} Last point. * @api */ getLastCoordinate() { return this.flatCoordinates.slice( this.flatCoordinates.length - this.stride ); } /** * Return the {@link import("./Geometry.js").GeometryLayout layout} of the geometry. * @return {import("./Geometry.js").GeometryLayout} Layout. * @api */ getLayout() { return this.layout; } /** * Create a simplified version of this geometry using the Douglas Peucker algorithm. * @param {number} squaredTolerance Squared tolerance. * @return {SimpleGeometry} Simplified geometry. */ getSimplifiedGeometry(squaredTolerance) { if (this.simplifiedGeometryRevision !== this.getRevision()) { this.simplifiedGeometryMaxMinSquaredTolerance = 0; this.simplifiedGeometryRevision = this.getRevision(); } // If squaredTolerance is negative or if we know that simplification will not // have any effect then just return this. if ( squaredTolerance < 0 || (this.simplifiedGeometryMaxMinSquaredTolerance !== 0 && squaredTolerance <= this.simplifiedGeometryMaxMinSquaredTolerance) ) { return this; } const simplifiedGeometry = this.getSimplifiedGeometryInternal(squaredTolerance); const simplifiedFlatCoordinates = simplifiedGeometry.getFlatCoordinates(); if (simplifiedFlatCoordinates.length < this.flatCoordinates.length) { return simplifiedGeometry; } // Simplification did not actually remove any coordinates. We now know // that any calls to getSimplifiedGeometry with a squaredTolerance less // than or equal to the current squaredTolerance will also not have any // effect. This allows us to short circuit simplification (saving CPU // cycles) and prevents the cache of simplified geometries from filling // up with useless identical copies of this geometry (saving memory). this.simplifiedGeometryMaxMinSquaredTolerance = squaredTolerance; return this; } /** * @param {number} squaredTolerance Squared tolerance. * @return {SimpleGeometry} Simplified geometry. * @protected */ getSimplifiedGeometryInternal(squaredTolerance) { return this; } /** * @return {number} Stride. */ getStride() { return this.stride; } /** * @param {import("./Geometry.js").GeometryLayout} layout Layout. * @param {Array} flatCoordinates Flat coordinates. */ setFlatCoordinates(layout, flatCoordinates) { this.stride = getStrideForLayout(layout); this.layout = layout; this.flatCoordinates = flatCoordinates; } /** * @abstract * @param {!Array<*>} coordinates Coordinates. * @param {import("./Geometry.js").GeometryLayout} [layout] Layout. */ setCoordinates(coordinates, layout) { abstract(); } /** * @param {import("./Geometry.js").GeometryLayout|undefined} layout Layout. * @param {Array<*>} coordinates Coordinates. * @param {number} nesting Nesting. * @protected */ setLayout(layout, coordinates, nesting) { /** @type {number} */ let stride; if (layout) { stride = getStrideForLayout(layout); } else { for (let i = 0; i < nesting; ++i) { if (coordinates.length === 0) { this.layout = 'XY'; this.stride = 2; return; } coordinates = /** @type {Array} */ (coordinates[0]); } stride = coordinates.length; layout = getLayoutForStride(stride); } this.layout = layout; this.stride = stride; } /** * Apply a transform function to the coordinates of the geometry. * The geometry is modified in place. * If you do not want the geometry modified in place, first `clone()` it and * then use this function on the clone. * @param {import("../proj.js").TransformFunction} transformFn Transform function. * Called with a flat array of geometry coordinates. * @api */ applyTransform(transformFn) { if (this.flatCoordinates) { transformFn(this.flatCoordinates, this.flatCoordinates, this.stride); this.changed(); } } /** * Rotate the geometry around a given coordinate. This modifies the geometry * coordinates in place. * @param {number} angle Rotation angle in counter-clockwise radians. * @param {import("../coordinate.js").Coordinate} anchor The rotation center. * @api */ rotate(angle, anchor) { const flatCoordinates = this.getFlatCoordinates(); if (flatCoordinates) { const stride = this.getStride(); rotate( flatCoordinates, 0, flatCoordinates.length, stride, angle, anchor, flatCoordinates ); this.changed(); } } /** * Scale the geometry (with an optional origin). This modifies the geometry * coordinates in place. * @param {number} sx The scaling factor in the x-direction. * @param {number} [sy] The scaling factor in the y-direction (defaults to sx). * @param {import("../coordinate.js").Coordinate} [anchor] The scale origin (defaults to the center * of the geometry extent). * @api */ scale(sx, sy, anchor) { if (sy === undefined) { sy = sx; } if (!anchor) { anchor = getCenter(this.getExtent()); } const flatCoordinates = this.getFlatCoordinates(); if (flatCoordinates) { const stride = this.getStride(); scale( flatCoordinates, 0, flatCoordinates.length, stride, sx, sy, anchor, flatCoordinates ); this.changed(); } } /** * Translate the geometry. This modifies the geometry coordinates in place. If * instead you want a new geometry, first `clone()` this geometry. * @param {number} deltaX Delta X. * @param {number} deltaY Delta Y. * @api */ translate(deltaX, deltaY) { const flatCoordinates = this.getFlatCoordinates(); if (flatCoordinates) { const stride = this.getStride(); translate( flatCoordinates, 0, flatCoordinates.length, stride, deltaX, deltaY, flatCoordinates ); this.changed(); } } } /** * @param {number} stride Stride. * @return {import("./Geometry.js").GeometryLayout} layout Layout. */ function getLayoutForStride(stride) { let layout; if (stride == 2) { layout = 'XY'; } else if (stride == 3) { layout = 'XYZ'; } else if (stride == 4) { layout = 'XYZM'; } return /** @type {import("./Geometry.js").GeometryLayout} */ (layout); } /** * @param {import("./Geometry.js").GeometryLayout} layout Layout. * @return {number} Stride. */ export function getStrideForLayout(layout) { let stride; if (layout == 'XY') { stride = 2; } else if (layout == 'XYZ' || layout == 'XYM') { stride = 3; } else if (layout == 'XYZM') { stride = 4; } return /** @type {number} */ (stride); } /** * @param {SimpleGeometry} simpleGeometry Simple geometry. * @param {import("../transform.js").Transform} transform Transform. * @param {Array} [dest] Destination. * @return {Array} Transformed flat coordinates. */ export function transformGeom2D(simpleGeometry, transform, dest) { const flatCoordinates = simpleGeometry.getFlatCoordinates(); if (!flatCoordinates) { return null; } const stride = simpleGeometry.getStride(); return transform2D( flatCoordinates, 0, flatCoordinates.length, stride, transform, dest ); } export default SimpleGeometry;