405 lines
11 KiB
JavaScript
405 lines
11 KiB
JavaScript
/**
|
||
* @module ol/coordinate
|
||
*/
|
||
import {modulo} from './math.js';
|
||
import {padNumber} from './string.js';
|
||
|
||
|
||
/**
|
||
* An array of numbers representing an xy coordinate. Example: `[16, 48]`.
|
||
* @typedef {Array<number>} Coordinate
|
||
* @api
|
||
*/
|
||
|
||
|
||
/**
|
||
* A function that takes a {@link module:ol/coordinate~Coordinate} and
|
||
* transforms it into a `{string}`.
|
||
*
|
||
* @typedef {function((Coordinate|undefined)): string} CoordinateFormat
|
||
* @api
|
||
*/
|
||
|
||
|
||
/**
|
||
* Add `delta` to `coordinate`. `coordinate` is modified in place and returned
|
||
* by the function.
|
||
*
|
||
* Example:
|
||
*
|
||
* import {add} from 'ol/coordinate';
|
||
*
|
||
* var coord = [7.85, 47.983333];
|
||
* add(coord, [-2, 4]);
|
||
* // coord is now [5.85, 51.983333]
|
||
*
|
||
* @param {Coordinate} coordinate Coordinate.
|
||
* @param {Coordinate} delta Delta.
|
||
* @return {Coordinate} The input coordinate adjusted by
|
||
* the given delta.
|
||
* @api
|
||
*/
|
||
export function add(coordinate, delta) {
|
||
coordinate[0] += +delta[0];
|
||
coordinate[1] += +delta[1];
|
||
return coordinate;
|
||
}
|
||
|
||
|
||
/**
|
||
* Calculates the point closest to the passed coordinate on the passed circle.
|
||
*
|
||
* @param {Coordinate} coordinate The coordinate.
|
||
* @param {import("./geom/Circle.js").default} circle The circle.
|
||
* @return {Coordinate} Closest point on the circumference.
|
||
*/
|
||
export function closestOnCircle(coordinate, circle) {
|
||
const r = circle.getRadius();
|
||
const center = circle.getCenter();
|
||
const x0 = center[0];
|
||
const y0 = center[1];
|
||
const x1 = coordinate[0];
|
||
const y1 = coordinate[1];
|
||
|
||
let dx = x1 - x0;
|
||
const dy = y1 - y0;
|
||
if (dx === 0 && dy === 0) {
|
||
dx = 1;
|
||
}
|
||
const d = Math.sqrt(dx * dx + dy * dy);
|
||
|
||
const x = x0 + r * dx / d;
|
||
const y = y0 + r * dy / d;
|
||
|
||
return [x, y];
|
||
}
|
||
|
||
|
||
/**
|
||
* Calculates the point closest to the passed coordinate on the passed segment.
|
||
* This is the foot of the perpendicular of the coordinate to the segment when
|
||
* the foot is on the segment, or the closest segment coordinate when the foot
|
||
* is outside the segment.
|
||
*
|
||
* @param {Coordinate} coordinate The coordinate.
|
||
* @param {Array<Coordinate>} segment The two coordinates
|
||
* of the segment.
|
||
* @return {Coordinate} The foot of the perpendicular of
|
||
* the coordinate to the segment.
|
||
*/
|
||
export function closestOnSegment(coordinate, segment) {
|
||
const x0 = coordinate[0];
|
||
const y0 = coordinate[1];
|
||
const start = segment[0];
|
||
const end = segment[1];
|
||
const x1 = start[0];
|
||
const y1 = start[1];
|
||
const x2 = end[0];
|
||
const y2 = end[1];
|
||
const dx = x2 - x1;
|
||
const dy = y2 - y1;
|
||
const along = (dx === 0 && dy === 0) ? 0 :
|
||
((dx * (x0 - x1)) + (dy * (y0 - y1))) / ((dx * dx + dy * dy) || 0);
|
||
let x, y;
|
||
if (along <= 0) {
|
||
x = x1;
|
||
y = y1;
|
||
} else if (along >= 1) {
|
||
x = x2;
|
||
y = y2;
|
||
} else {
|
||
x = x1 + along * dx;
|
||
y = y1 + along * dy;
|
||
}
|
||
return [x, y];
|
||
}
|
||
|
||
|
||
/**
|
||
* Returns a {@link module:ol/coordinate~CoordinateFormat} function that can be
|
||
* used to format
|
||
* a {Coordinate} to a string.
|
||
*
|
||
* Example without specifying the fractional digits:
|
||
*
|
||
* import {createStringXY} from 'ol/coordinate';
|
||
*
|
||
* var coord = [7.85, 47.983333];
|
||
* var stringifyFunc = createStringXY();
|
||
* var out = stringifyFunc(coord);
|
||
* // out is now '8, 48'
|
||
*
|
||
* Example with explicitly specifying 2 fractional digits:
|
||
*
|
||
* import {createStringXY} from 'ol/coordinate';
|
||
*
|
||
* var coord = [7.85, 47.983333];
|
||
* var stringifyFunc = createStringXY(2);
|
||
* var out = stringifyFunc(coord);
|
||
* // out is now '7.85, 47.98'
|
||
*
|
||
* @param {number=} opt_fractionDigits The number of digits to include
|
||
* after the decimal point. Default is `0`.
|
||
* @return {CoordinateFormat} Coordinate format.
|
||
* @api
|
||
*/
|
||
export function createStringXY(opt_fractionDigits) {
|
||
return (
|
||
/**
|
||
* @param {Coordinate} coordinate Coordinate.
|
||
* @return {string} String XY.
|
||
*/
|
||
function(coordinate) {
|
||
return toStringXY(coordinate, opt_fractionDigits);
|
||
}
|
||
);
|
||
}
|
||
|
||
|
||
/**
|
||
* @param {string} hemispheres Hemispheres.
|
||
* @param {number} degrees Degrees.
|
||
* @param {number=} opt_fractionDigits The number of digits to include
|
||
* after the decimal point. Default is `0`.
|
||
* @return {string} String.
|
||
*/
|
||
export function degreesToStringHDMS(hemispheres, degrees, opt_fractionDigits) {
|
||
const normalizedDegrees = modulo(degrees + 180, 360) - 180;
|
||
const x = Math.abs(3600 * normalizedDegrees);
|
||
const dflPrecision = opt_fractionDigits || 0;
|
||
const precision = Math.pow(10, dflPrecision);
|
||
|
||
let deg = Math.floor(x / 3600);
|
||
let min = Math.floor((x - deg * 3600) / 60);
|
||
let sec = x - (deg * 3600) - (min * 60);
|
||
sec = Math.ceil(sec * precision) / precision;
|
||
|
||
if (sec >= 60) {
|
||
sec = 0;
|
||
min += 1;
|
||
}
|
||
|
||
if (min >= 60) {
|
||
min = 0;
|
||
deg += 1;
|
||
}
|
||
|
||
return deg + '\u00b0 ' + padNumber(min, 2) + '\u2032 ' +
|
||
padNumber(sec, 2, dflPrecision) + '\u2033' +
|
||
(normalizedDegrees == 0 ? '' : ' ' + hemispheres.charAt(normalizedDegrees < 0 ? 1 : 0));
|
||
}
|
||
|
||
|
||
/**
|
||
* Transforms the given {@link module:ol/coordinate~Coordinate} to a string
|
||
* using the given string template. The strings `{x}` and `{y}` in the template
|
||
* will be replaced with the first and second coordinate values respectively.
|
||
*
|
||
* Example without specifying the fractional digits:
|
||
*
|
||
* import {format} from 'ol/coordinate';
|
||
*
|
||
* var coord = [7.85, 47.983333];
|
||
* var template = 'Coordinate is ({x}|{y}).';
|
||
* var out = format(coord, template);
|
||
* // out is now 'Coordinate is (8|48).'
|
||
*
|
||
* Example explicitly specifying the fractional digits:
|
||
*
|
||
* import {format} from 'ol/coordinate';
|
||
*
|
||
* var coord = [7.85, 47.983333];
|
||
* var template = 'Coordinate is ({x}|{y}).';
|
||
* var out = format(coord, template, 2);
|
||
* // out is now 'Coordinate is (7.85|47.98).'
|
||
*
|
||
* @param {Coordinate} coordinate Coordinate.
|
||
* @param {string} template A template string with `{x}` and `{y}` placeholders
|
||
* that will be replaced by first and second coordinate values.
|
||
* @param {number=} opt_fractionDigits The number of digits to include
|
||
* after the decimal point. Default is `0`.
|
||
* @return {string} Formatted coordinate.
|
||
* @api
|
||
*/
|
||
export function format(coordinate, template, opt_fractionDigits) {
|
||
if (coordinate) {
|
||
return template
|
||
.replace('{x}', coordinate[0].toFixed(opt_fractionDigits))
|
||
.replace('{y}', coordinate[1].toFixed(opt_fractionDigits));
|
||
} else {
|
||
return '';
|
||
}
|
||
}
|
||
|
||
|
||
/**
|
||
* @param {Coordinate} coordinate1 First coordinate.
|
||
* @param {Coordinate} coordinate2 Second coordinate.
|
||
* @return {boolean} The two coordinates are equal.
|
||
*/
|
||
export function equals(coordinate1, coordinate2) {
|
||
let equals = true;
|
||
for (let i = coordinate1.length - 1; i >= 0; --i) {
|
||
if (coordinate1[i] != coordinate2[i]) {
|
||
equals = false;
|
||
break;
|
||
}
|
||
}
|
||
return equals;
|
||
}
|
||
|
||
|
||
/**
|
||
* Rotate `coordinate` by `angle`. `coordinate` is modified in place and
|
||
* returned by the function.
|
||
*
|
||
* Example:
|
||
*
|
||
* import {rotate} from 'ol/coordinate';
|
||
*
|
||
* var coord = [7.85, 47.983333];
|
||
* var rotateRadians = Math.PI / 2; // 90 degrees
|
||
* rotate(coord, rotateRadians);
|
||
* // coord is now [-47.983333, 7.85]
|
||
*
|
||
* @param {Coordinate} coordinate Coordinate.
|
||
* @param {number} angle Angle in radian.
|
||
* @return {Coordinate} Coordinate.
|
||
* @api
|
||
*/
|
||
export function rotate(coordinate, angle) {
|
||
const cosAngle = Math.cos(angle);
|
||
const sinAngle = Math.sin(angle);
|
||
const x = coordinate[0] * cosAngle - coordinate[1] * sinAngle;
|
||
const y = coordinate[1] * cosAngle + coordinate[0] * sinAngle;
|
||
coordinate[0] = x;
|
||
coordinate[1] = y;
|
||
return coordinate;
|
||
}
|
||
|
||
|
||
/**
|
||
* Scale `coordinate` by `scale`. `coordinate` is modified in place and returned
|
||
* by the function.
|
||
*
|
||
* Example:
|
||
*
|
||
* import {scale as scaleCoordinate} from 'ol/coordinate';
|
||
*
|
||
* var coord = [7.85, 47.983333];
|
||
* var scale = 1.2;
|
||
* scaleCoordinate(coord, scale);
|
||
* // coord is now [9.42, 57.5799996]
|
||
*
|
||
* @param {Coordinate} coordinate Coordinate.
|
||
* @param {number} scale Scale factor.
|
||
* @return {Coordinate} Coordinate.
|
||
*/
|
||
export function scale(coordinate, scale) {
|
||
coordinate[0] *= scale;
|
||
coordinate[1] *= scale;
|
||
return coordinate;
|
||
}
|
||
|
||
|
||
/**
|
||
* @param {Coordinate} coord1 First coordinate.
|
||
* @param {Coordinate} coord2 Second coordinate.
|
||
* @return {number} Squared distance between coord1 and coord2.
|
||
*/
|
||
export function squaredDistance(coord1, coord2) {
|
||
const dx = coord1[0] - coord2[0];
|
||
const dy = coord1[1] - coord2[1];
|
||
return dx * dx + dy * dy;
|
||
}
|
||
|
||
|
||
/**
|
||
* @param {Coordinate} coord1 First coordinate.
|
||
* @param {Coordinate} coord2 Second coordinate.
|
||
* @return {number} Distance between coord1 and coord2.
|
||
*/
|
||
export function distance(coord1, coord2) {
|
||
return Math.sqrt(squaredDistance(coord1, coord2));
|
||
}
|
||
|
||
|
||
/**
|
||
* Calculate the squared distance from a coordinate to a line segment.
|
||
*
|
||
* @param {Coordinate} coordinate Coordinate of the point.
|
||
* @param {Array<Coordinate>} segment Line segment (2
|
||
* coordinates).
|
||
* @return {number} Squared distance from the point to the line segment.
|
||
*/
|
||
export function squaredDistanceToSegment(coordinate, segment) {
|
||
return squaredDistance(coordinate,
|
||
closestOnSegment(coordinate, segment));
|
||
}
|
||
|
||
|
||
/**
|
||
* Format a geographic coordinate with the hemisphere, degrees, minutes, and
|
||
* seconds.
|
||
*
|
||
* Example without specifying fractional digits:
|
||
*
|
||
* import {toStringHDMS} from 'ol/coordinate';
|
||
*
|
||
* var coord = [7.85, 47.983333];
|
||
* var out = toStringHDMS(coord);
|
||
* // out is now '47° 58′ 60″ N 7° 50′ 60″ E'
|
||
*
|
||
* Example explicitly specifying 1 fractional digit:
|
||
*
|
||
* import {toStringHDMS} from 'ol/coordinate';
|
||
*
|
||
* var coord = [7.85, 47.983333];
|
||
* var out = toStringHDMS(coord, 1);
|
||
* // out is now '47° 58′ 60.0″ N 7° 50′ 60.0″ E'
|
||
*
|
||
* @param {Coordinate} coordinate Coordinate.
|
||
* @param {number=} opt_fractionDigits The number of digits to include
|
||
* after the decimal point. Default is `0`.
|
||
* @return {string} Hemisphere, degrees, minutes and seconds.
|
||
* @api
|
||
*/
|
||
export function toStringHDMS(coordinate, opt_fractionDigits) {
|
||
if (coordinate) {
|
||
return degreesToStringHDMS('NS', coordinate[1], opt_fractionDigits) + ' ' +
|
||
degreesToStringHDMS('EW', coordinate[0], opt_fractionDigits);
|
||
} else {
|
||
return '';
|
||
}
|
||
}
|
||
|
||
|
||
/**
|
||
* Format a coordinate as a comma delimited string.
|
||
*
|
||
* Example without specifying fractional digits:
|
||
*
|
||
* import {toStringXY} from 'ol/coordinate';
|
||
*
|
||
* var coord = [7.85, 47.983333];
|
||
* var out = toStringXY(coord);
|
||
* // out is now '8, 48'
|
||
*
|
||
* Example explicitly specifying 1 fractional digit:
|
||
*
|
||
* import {toStringXY} from 'ol/coordinate';
|
||
*
|
||
* var coord = [7.85, 47.983333];
|
||
* var out = toStringXY(coord, 1);
|
||
* // out is now '7.8, 48.0'
|
||
*
|
||
* @param {Coordinate} coordinate Coordinate.
|
||
* @param {number=} opt_fractionDigits The number of digits to include
|
||
* after the decimal point. Default is `0`.
|
||
* @return {string} XY.
|
||
* @api
|
||
*/
|
||
export function toStringXY(coordinate, opt_fractionDigits) {
|
||
return format(coordinate, '{x}, {y}', opt_fractionDigits);
|
||
}
|