/** * @module ol/geom/LineString */ import SimpleGeometry from './SimpleGeometry.js'; import {assignClosestPoint, maxSquaredDelta} from './flat/closest.js'; import {closestSquaredDistanceXY} from '../extent.js'; import {deflateCoordinates} from './flat/deflate.js'; import {douglasPeucker} from './flat/simplify.js'; import {extend} from '../array.js'; import {forEach as forEachSegment} from './flat/segments.js'; import {inflateCoordinates} from './flat/inflate.js'; import {interpolatePoint, lineStringCoordinateAtM} from './flat/interpolate.js'; import {intersectsLineString} from './flat/intersectsextent.js'; import {lineStringLength} from './flat/length.js'; /** * @classdesc * Linestring geometry. * * @api */ class LineString extends SimpleGeometry { /** * @param {Array|Array} coordinates Coordinates. * For internal use, flat coordinates in combination with `layout` are also accepted. * @param {import("./Geometry.js").GeometryLayout} [layout] Layout. */ constructor(coordinates, layout) { super(); /** * @private * @type {import("../coordinate.js").Coordinate} */ this.flatMidpoint_ = null; /** * @private * @type {number} */ this.flatMidpointRevision_ = -1; /** * @private * @type {number} */ this.maxDelta_ = -1; /** * @private * @type {number} */ this.maxDeltaRevision_ = -1; if (layout !== undefined && !Array.isArray(coordinates[0])) { this.setFlatCoordinates( layout, /** @type {Array} */ (coordinates) ); } else { this.setCoordinates( /** @type {Array} */ ( coordinates ), layout ); } } /** * Append the passed coordinate to the coordinates of the linestring. * @param {import("../coordinate.js").Coordinate} coordinate Coordinate. * @api */ appendCoordinate(coordinate) { if (!this.flatCoordinates) { this.flatCoordinates = coordinate.slice(); } else { extend(this.flatCoordinates, coordinate); } this.changed(); } /** * Make a complete copy of the geometry. * @return {!LineString} Clone. * @api */ clone() { const lineString = new LineString( this.flatCoordinates.slice(), this.layout ); lineString.applyProperties(this); return lineString; } /** * @param {number} x X. * @param {number} y Y. * @param {import("../coordinate.js").Coordinate} closestPoint Closest point. * @param {number} minSquaredDistance Minimum squared distance. * @return {number} Minimum squared distance. */ closestPointXY(x, y, closestPoint, minSquaredDistance) { if (minSquaredDistance < closestSquaredDistanceXY(this.getExtent(), x, y)) { return minSquaredDistance; } if (this.maxDeltaRevision_ != this.getRevision()) { this.maxDelta_ = Math.sqrt( maxSquaredDelta( this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, 0 ) ); this.maxDeltaRevision_ = this.getRevision(); } return assignClosestPoint( this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, this.maxDelta_, false, x, y, closestPoint, minSquaredDistance ); } /** * Iterate over each segment, calling the provided callback. * If the callback returns a truthy value the function returns that * value immediately. Otherwise the function returns `false`. * * @param {function(this: S, import("../coordinate.js").Coordinate, import("../coordinate.js").Coordinate): T} callback Function * called for each segment. The function will receive two arguments, the start and end coordinates of the segment. * @return {T|boolean} Value. * @template T,S * @api */ forEachSegment(callback) { return forEachSegment( this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, callback ); } /** * Returns the coordinate at `m` using linear interpolation, or `null` if no * such coordinate exists. * * `extrapolate` controls extrapolation beyond the range of Ms in the * MultiLineString. If `extrapolate` is `true` then Ms less than the first * M will return the first coordinate and Ms greater than the last M will * return the last coordinate. * * @param {number} m M. * @param {boolean} [extrapolate] Extrapolate. Default is `false`. * @return {import("../coordinate.js").Coordinate|null} Coordinate. * @api */ getCoordinateAtM(m, extrapolate) { if (this.layout != 'XYM' && this.layout != 'XYZM') { return null; } extrapolate = extrapolate !== undefined ? extrapolate : false; return lineStringCoordinateAtM( this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, m, extrapolate ); } /** * Return the coordinates of the linestring. * @return {Array} Coordinates. * @api */ getCoordinates() { return inflateCoordinates( this.flatCoordinates, 0, this.flatCoordinates.length, this.stride ); } /** * Return the coordinate at the provided fraction along the linestring. * The `fraction` is a number between 0 and 1, where 0 is the start of the * linestring and 1 is the end. * @param {number} fraction Fraction. * @param {import("../coordinate.js").Coordinate} [dest] Optional coordinate whose values will * be modified. If not provided, a new coordinate will be returned. * @return {import("../coordinate.js").Coordinate} Coordinate of the interpolated point. * @api */ getCoordinateAt(fraction, dest) { return interpolatePoint( this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, fraction, dest, this.stride ); } /** * Return the length of the linestring on projected plane. * @return {number} Length (on projected plane). * @api */ getLength() { return lineStringLength( this.flatCoordinates, 0, this.flatCoordinates.length, this.stride ); } /** * @return {Array} Flat midpoint. */ getFlatMidpoint() { if (this.flatMidpointRevision_ != this.getRevision()) { this.flatMidpoint_ = this.getCoordinateAt(0.5, this.flatMidpoint_); this.flatMidpointRevision_ = this.getRevision(); } return this.flatMidpoint_; } /** * @param {number} squaredTolerance Squared tolerance. * @return {LineString} Simplified LineString. * @protected */ getSimplifiedGeometryInternal(squaredTolerance) { const simplifiedFlatCoordinates = []; simplifiedFlatCoordinates.length = douglasPeucker( this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, squaredTolerance, simplifiedFlatCoordinates, 0 ); return new LineString(simplifiedFlatCoordinates, 'XY'); } /** * Get the type of this geometry. * @return {import("./Geometry.js").Type} Geometry type. * @api */ getType() { return 'LineString'; } /** * Test if the geometry and the passed extent intersect. * @param {import("../extent.js").Extent} extent Extent. * @return {boolean} `true` if the geometry and the extent intersect. * @api */ intersectsExtent(extent) { return intersectsLineString( this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, extent ); } /** * Set the coordinates of the linestring. * @param {!Array} coordinates Coordinates. * @param {import("./Geometry.js").GeometryLayout} [layout] Layout. * @api */ setCoordinates(coordinates, layout) { this.setLayout(layout, coordinates, 1); if (!this.flatCoordinates) { this.flatCoordinates = []; } this.flatCoordinates.length = deflateCoordinates( this.flatCoordinates, 0, coordinates, this.stride ); this.changed(); } } export default LineString;