@terrestris/ol-util
Version:
A set of helper classes for working with openLayers
253 lines • 9.78 kB
JavaScript
import _isNil from 'lodash/isNil';
import OlGeomCircle from 'ol/geom/Circle';
import { getArea, getLength } from 'ol/sphere';
/**
* This class provides some static methods which might be helpful when working
* with measurements.
*
* @class MeasureUtil
*/
class MeasureUtil {
/**
* Get the length of a OlGeomLineString.
*
* @param {OlGeomLineString} line The drawn line.
* @param {OlMap} map An OlMap.
* @param {boolean} geodesic Is the measurement geodesic (default is true).
* @param {number} radius Sphere radius. By default, the radius of the earth
* is used (Clarke 1866 Authalic Sphere, 6371008.8).
* @param {number} decimalPrecision Set the decimal precision on length value
* for non-geodesic map (default value 6)
*
* @return {number} The length of line in meters.
*/
static getLength(line, map, geodesic = true, radius = 6371008.8, decimalPrecision = 6) {
const decimalHelper = Math.pow(10, decimalPrecision);
if (geodesic) {
const opts = {
projection: map.getView().getProjection().getCode(),
radius
};
return getLength(line, opts);
}
else {
return Math.round(line.getLength() * decimalHelper) / decimalHelper;
}
}
/**
* Format length output for the tooltip.
*
* @param {OlGeomLineString} line The drawn line.
* @param {OlMap} map An OlMap.
* @param {number} decimalPlacesInToolTips How many decimal places will be
* allowed for the measure tooltips
* @param {boolean} geodesic Is the measurement geodesic (default is true).
*
* @return {string} The formatted length of the line (units: km, m or mm).
*/
static formatLength(line, map, decimalPlacesInToolTips, geodesic = true) {
const decimalHelper = Math.pow(10, decimalPlacesInToolTips);
const length = MeasureUtil.getLength(line, map, geodesic);
let output;
if (length > 1000) {
output = (Math.round(length / 1000 * decimalHelper) /
decimalHelper) + ' km';
}
else if (length > 1) {
output = (Math.round(length * decimalHelper) / decimalHelper) +
' m';
}
else {
output = (Math.round(length * 1000 * decimalHelper) / decimalHelper) +
' mm';
}
return output;
}
/**
* Get the area of an OlGeomPolygon.
*
* @param {OlGeomPolygon} polygon The drawn polygon.
* @param {OlMap} map An OlMap.
* @param {boolean} geodesic Is the measurement geodesic (default is true).
* @param {number} radius Sphere radius. By default, the radius of the earth
* is used (Clarke 1866 Authalic Sphere, 6371008.8).
*
* @return {number} The area of the polygon in square meter.
*/
static getArea(polygon, map, geodesic = true, radius = 6371008.8) {
if (geodesic) {
const opts = {
projection: map.getView().getProjection().getCode(),
radius
};
return getArea(polygon, opts);
}
else {
return polygon.getArea();
}
}
/**
* Get the estimated area of an OlGeomCircle.
*
* @param {OlGeomCircle} circleGeom The drawn circle.
* @param {OlMap} map An OlMap.
*
* @return {number} The area of the circle in square meter.
*/
static getAreaOfCircle(circleGeom, map) {
if (_isNil(map.getView().getProjection())) {
return NaN;
}
const sphericalUnits = ['radians', 'degrees'];
const projectionUnits = map.getView().getProjection().getUnits();
const useSpherical = sphericalUnits.includes(projectionUnits);
if (useSpherical) {
// see https://math.stackexchange.com/questions/1832110/area-of-a-circle-on-sphere
// the radius of the earth - Clarke 1866 authalic Sphere
const earthRadius = 6371008.8;
const radius = circleGeom.getRadius();
let area = 2.0 * Math.PI * Math.pow(earthRadius, 2);
area *= (1 - Math.cos(radius / earthRadius));
return area;
}
else {
return Math.PI * Math.pow(circleGeom.getRadius(), 2);
}
}
/**
* Format area output for the tooltip.
*
* @param {OlGeomPolygon | OlGeomCircle} geom The drawn geometry (circle or polygon).
* @param {OlMap} map An OlMap.
* @param {number} decimalPlacesInToolTips How many decimal places will be
* allowed for the measure tooltips.
* @param {boolean} geodesic Is the measurement geodesic.
*
* @return {string} The formatted area of the polygon.
*/
static formatArea(geom, map, decimalPlacesInToolTips, geodesic = true) {
const decimalHelper = Math.pow(10, decimalPlacesInToolTips);
let area;
if (geom instanceof OlGeomCircle) {
area = MeasureUtil.getAreaOfCircle(geom, map);
}
else {
area = MeasureUtil.getArea(geom, map, geodesic);
}
let output;
if (area > 10000) {
output = (Math.round(area / 1000000 * decimalHelper) /
decimalHelper) + ' km<sup>2</sup>';
}
else if (area > 0.01) {
output = (Math.round(area * decimalHelper) / decimalHelper) +
' m<sup>2</sup>';
}
else {
output = (Math.round(area * 1000000 * decimalHelper) / decimalHelper) +
' mm<sup>2</sup>';
}
return output;
}
/**
* Determine the angle between two coordinates. The angle will be between
* -180° and 180°, with 0° being in the east. The angle will increase
* counter-clockwise.
*
* Inspired by https://stackoverflow.com/a/31136507
*
* @param {Array<number>} start The start coordinates of the line with the
* x-coordinate being at index `0` and y-coordinate being at index `1`.
* @param {Array<number>} end The end coordinates of the line with the
* x-coordinate being at index `0` and y-coordinate being at index `1`.
*
* @return {number} the angle in degrees, ranging from -180° to 180°.
*/
static angle(start, end) {
const dx = start[0] - end[0];
const dy = start[1] - end[1];
// range (-PI, PI]
let theta = Math.atan2(dy, dx);
// rads to degs, range (-180, 180]
theta *= 180 / Math.PI;
return theta;
}
/**
* Determine the angle between two coordinates. The angle will be between
* 0° and 360°, with 0° being in the east. The angle will increase
* counter-clockwise.
*
* Inspired by https://stackoverflow.com/a/31136507
*
* @param {Array<number>} start The start coordinates of the line with the
* x-coordinate being at index `0` and y-coordinate being at index `1`.
* @param {Array<number>} end The end coordinates of the line with the
* x-coordinate being at index `0` and y-coordinate being at index `1`.
*
* @return {number} the angle in degrees, ranging from 0° and 360°.
*/
static angle360(start, end) {
// range (-180, 180]
let theta = MeasureUtil.angle(start, end);
if (theta < 0) {
// range [0, 360)
theta = 360 + theta;
}
return theta;
}
/**
* Given an angle between 0° and 360° this angle returns the exact opposite
* of the angle, e.g. for 90° you'll get back 270°. This effectively turns
* the direction of the angle from counter-clockwise to clockwise.
*
* @param {number} angle360 The input angle obtained counter-clockwise.
*
* @return {number} The clockwise angle.
*/
static makeClockwise(angle360) {
return 360 - angle360;
}
/**
* This methods adds an offset of 90° to an counter-clockwise increasing
* angle of a line so that the origin (0°) lies at the top (in the north).
*
* @param {number} angle360 The input angle obtained counter-clockwise, with
* 0° degrees being in the east.
*
* @return {number} The adjusted angle, with 0° being in the north.
*/
static makeZeroDegreesAtNorth(angle360) {
let corrected = angle360 + 90;
if (corrected > 360) {
corrected = corrected - 360;
}
return corrected;
}
/**
* Returns the angle of the passed linestring in degrees, with 'N' being the
* 0°-line and the angle increases in clockwise direction.
*
* @param {OlGeomLineString} line The linestring to get the
* angle from. As this line is coming from our internal draw
* interaction, we know that it will only consist of two points.
* @param {number} decimalPlacesInToolTips How many decimal places will be
* allowed for the measure tooltips.
*
* @return {string} The formatted angle of the line.
*/
static formatAngle(line, decimalPlacesInToolTips = 2) {
const coords = line.getCoordinates();
const numCoords = coords.length;
if (numCoords < 2) {
return '';
}
const lastPoint = coords[numCoords - 1];
const prevPoint = coords[numCoords - 2];
let angle = MeasureUtil.angle360(prevPoint, lastPoint);
angle = MeasureUtil.makeZeroDegreesAtNorth(angle);
angle = MeasureUtil.makeClockwise(angle);
return `${angle.toFixed(decimalPlacesInToolTips)}°`;
}
}
export default MeasureUtil;
//# sourceMappingURL=MeasureUtil.js.map