point.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.Point = void 0;
  4. const util_1 = require("./util");
  5. const angle_1 = require("./angle");
  6. const geometry_1 = require("./geometry");
  7. class Point extends geometry_1.Geometry {
  8. constructor(x, y) {
  9. super();
  10. this.x = x == null ? 0 : x;
  11. this.y = y == null ? 0 : y;
  12. }
  13. /**
  14. * Rounds the point to the given precision.
  15. */
  16. round(precision = 0) {
  17. this.x = util_1.GeometryUtil.round(this.x, precision);
  18. this.y = util_1.GeometryUtil.round(this.y, precision);
  19. return this;
  20. }
  21. add(x, y) {
  22. const p = Point.create(x, y);
  23. this.x += p.x;
  24. this.y += p.y;
  25. return this;
  26. }
  27. update(x, y) {
  28. const p = Point.create(x, y);
  29. this.x = p.x;
  30. this.y = p.y;
  31. return this;
  32. }
  33. translate(dx, dy) {
  34. const t = Point.create(dx, dy);
  35. this.x += t.x;
  36. this.y += t.y;
  37. return this;
  38. }
  39. /**
  40. * Rotate the point by `degree` around `center`.
  41. */
  42. rotate(degree, center) {
  43. const p = Point.rotate(this, degree, center);
  44. this.x = p.x;
  45. this.y = p.y;
  46. return this;
  47. }
  48. /**
  49. * Scale point by `sx` and `sy` around the given `origin`. If origin is
  50. * not specified, the point is scaled around `0, 0`.
  51. */
  52. scale(sx, sy, origin = new Point()) {
  53. const ref = Point.create(origin);
  54. this.x = ref.x + sx * (this.x - ref.x);
  55. this.y = ref.y + sy * (this.y - ref.y);
  56. return this;
  57. }
  58. /**
  59. * Chooses the point closest to this point from among `points`. If `points`
  60. * is an empty array, `null` is returned.
  61. */
  62. closest(points) {
  63. if (points.length === 1) {
  64. return Point.create(points[0]);
  65. }
  66. let ret = null;
  67. let min = Infinity;
  68. points.forEach((p) => {
  69. const dist = this.squaredDistance(p);
  70. if (dist < min) {
  71. ret = p;
  72. min = dist;
  73. }
  74. });
  75. return ret ? Point.create(ret) : null;
  76. }
  77. /**
  78. * Returns the distance between the point and another point `p`.
  79. */
  80. distance(p) {
  81. return Math.sqrt(this.squaredDistance(p));
  82. }
  83. /**
  84. * Returns the squared distance between the point and another point `p`.
  85. *
  86. * Useful for distance comparisons in which real distance is not necessary
  87. * (saves one `Math.sqrt()` operation).
  88. */
  89. squaredDistance(p) {
  90. const ref = Point.create(p);
  91. const dx = this.x - ref.x;
  92. const dy = this.y - ref.y;
  93. return dx * dx + dy * dy;
  94. }
  95. manhattanDistance(p) {
  96. const ref = Point.create(p);
  97. return Math.abs(ref.x - this.x) + Math.abs(ref.y - this.y);
  98. }
  99. /**
  100. * Returns the magnitude of the point vector.
  101. *
  102. * @see http://en.wikipedia.org/wiki/Magnitude_(mathematics)
  103. */
  104. magnitude() {
  105. return Math.sqrt(this.x * this.x + this.y * this.y) || 0.01;
  106. }
  107. /**
  108. * Returns the angle(in degrees) between vector from this point to `p` and
  109. * the x-axis.
  110. */
  111. theta(p = new Point()) {
  112. const ref = Point.create(p);
  113. const y = -(ref.y - this.y); // invert the y-axis.
  114. const x = ref.x - this.x;
  115. let rad = Math.atan2(y, x);
  116. // Correction for III. and IV. quadrant.
  117. if (rad < 0) {
  118. rad = 2 * Math.PI + rad;
  119. }
  120. return (180 * rad) / Math.PI;
  121. }
  122. /**
  123. * Returns the angle(in degrees) between vector from this point to `p1` and
  124. * the vector from this point to `p2`.
  125. *
  126. * The ordering of points `p1` and `p2` is important.
  127. *
  128. * The function returns a value between `0` and `180` when the angle (in the
  129. * direction from `p1` to `p2`) is clockwise, and a value between `180` and
  130. * `360` when the angle is counterclockwise.
  131. *
  132. * Returns `NaN` if either of the points `p1` and `p2` is equal with this point.
  133. */
  134. angleBetween(p1, p2) {
  135. if (this.equals(p1) || this.equals(p2)) {
  136. return NaN;
  137. }
  138. let angle = this.theta(p2) - this.theta(p1);
  139. if (angle < 0) {
  140. angle += 360;
  141. }
  142. return angle;
  143. }
  144. /**
  145. * Returns the angle(in degrees) between the line from `(0,0)` and this point
  146. * and the line from `(0,0)` to `p`.
  147. *
  148. * The function returns a value between `0` and `180` when the angle (in the
  149. * direction from this point to `p`) is clockwise, and a value between `180`
  150. * and `360` when the angle is counterclockwise. Returns `NaN` if called from
  151. * point `(0,0)` or if `p` is `(0,0)`.
  152. */
  153. vectorAngle(p) {
  154. const zero = new Point(0, 0);
  155. return zero.angleBetween(this, p);
  156. }
  157. /**
  158. * Converts rectangular to polar coordinates.
  159. */
  160. toPolar(origin) {
  161. this.update(Point.toPolar(this, origin));
  162. return this;
  163. }
  164. /**
  165. * Returns the change in angle(in degrees) that is the result of moving the
  166. * point from its previous position to its current position.
  167. *
  168. * More specifically, this function computes the angle between the line from
  169. * the ref point to the previous position of this point(i.e. current position
  170. * `-dx`, `-dy`) and the line from the `ref` point to the current position of
  171. * this point.
  172. *
  173. * The function returns a positive value between `0` and `180` when the angle
  174. * (in the direction from previous position of this point to its current
  175. * position) is clockwise, and a negative value between `0` and `-180` when
  176. * the angle is counterclockwise.
  177. *
  178. * The function returns `0` if the previous and current positions of this
  179. * point are the same (i.e. both `dx` and `dy` are `0`).
  180. */
  181. changeInAngle(dx, dy, ref = new Point()) {
  182. // Revert the translation and measure the change in angle around x-axis.
  183. return this.clone().translate(-dx, -dy).theta(ref) - this.theta(ref);
  184. }
  185. /**
  186. * If the point lies outside the rectangle `rect`, adjust the point so that
  187. * it becomes the nearest point on the boundary of `rect`.
  188. */
  189. adhereToRect(rect) {
  190. if (!util_1.GeometryUtil.containsPoint(rect, this)) {
  191. this.x = Math.min(Math.max(this.x, rect.x), rect.x + rect.width);
  192. this.y = Math.min(Math.max(this.y, rect.y), rect.y + rect.height);
  193. }
  194. return this;
  195. }
  196. /**
  197. * Returns the bearing(cardinal direction) between me and the given point.
  198. *
  199. * @see https://en.wikipedia.org/wiki/Cardinal_direction
  200. */
  201. bearing(p) {
  202. const ref = Point.create(p);
  203. const lat1 = angle_1.Angle.toRad(this.y);
  204. const lat2 = angle_1.Angle.toRad(ref.y);
  205. const lon1 = this.x;
  206. const lon2 = ref.x;
  207. const dLon = angle_1.Angle.toRad(lon2 - lon1);
  208. const y = Math.sin(dLon) * Math.cos(lat2);
  209. const x = Math.cos(lat1) * Math.sin(lat2) -
  210. Math.sin(lat1) * Math.cos(lat2) * Math.cos(dLon);
  211. const brng = angle_1.Angle.toDeg(Math.atan2(y, x));
  212. const bearings = ['NE', 'E', 'SE', 'S', 'SW', 'W', 'NW', 'N'];
  213. let index = brng - 22.5;
  214. if (index < 0) {
  215. index += 360;
  216. }
  217. index = parseInt((index / 45), 10);
  218. return bearings[index];
  219. }
  220. /**
  221. * Returns the cross product of the vector from me to `p1` and the vector
  222. * from me to `p2`.
  223. *
  224. * The left-hand rule is used because the coordinate system is left-handed.
  225. */
  226. cross(p1, p2) {
  227. if (p1 != null && p2 != null) {
  228. const a = Point.create(p1);
  229. const b = Point.create(p2);
  230. return (b.x - this.x) * (a.y - this.y) - (b.y - this.y) * (a.x - this.x);
  231. }
  232. return NaN;
  233. }
  234. /**
  235. * Returns the dot product of this point with given other point.
  236. */
  237. dot(p) {
  238. const ref = Point.create(p);
  239. return this.x * ref.x + this.y * ref.y;
  240. }
  241. diff(dx, dy) {
  242. if (typeof dx === 'number') {
  243. return new Point(this.x - dx, this.y - dy);
  244. }
  245. const p = Point.create(dx);
  246. return new Point(this.x - p.x, this.y - p.y);
  247. }
  248. /**
  249. * Returns an interpolation between me and point `p` for a parametert in
  250. * the closed interval `[0, 1]`.
  251. */
  252. lerp(p, t) {
  253. const ref = Point.create(p);
  254. return new Point((1 - t) * this.x + t * ref.x, (1 - t) * this.y + t * ref.y);
  255. }
  256. /**
  257. * Normalize the point vector, scale the line segment between `(0, 0)`
  258. * and the point in order for it to have the given length. If length is
  259. * not specified, it is considered to be `1`; in that case, a unit vector
  260. * is computed.
  261. */
  262. normalize(length = 1) {
  263. const scale = length / this.magnitude();
  264. return this.scale(scale, scale);
  265. }
  266. /**
  267. * Moves this point along the line starting from `ref` to this point by a
  268. * certain `distance`.
  269. */
  270. move(ref, distance) {
  271. const p = Point.create(ref);
  272. const rad = angle_1.Angle.toRad(p.theta(this));
  273. return this.translate(Math.cos(rad) * distance, -Math.sin(rad) * distance);
  274. }
  275. /**
  276. * Returns a point that is the reflection of me with the center of inversion
  277. * in `ref` point.
  278. */
  279. reflection(ref) {
  280. return Point.create(ref).move(this, this.distance(ref));
  281. }
  282. snapToGrid(gx, gy) {
  283. this.x = util_1.GeometryUtil.snapToGrid(this.x, gx);
  284. this.y = util_1.GeometryUtil.snapToGrid(this.y, gy == null ? gx : gy);
  285. return this;
  286. }
  287. equals(p) {
  288. const ref = Point.create(p);
  289. return ref != null && ref.x === this.x && ref.y === this.y;
  290. }
  291. clone() {
  292. return Point.clone(this);
  293. }
  294. /**
  295. * Returns the point as a simple JSON object. For example: `{ x: 0, y: 0 }`.
  296. */
  297. toJSON() {
  298. return Point.toJSON(this);
  299. }
  300. serialize() {
  301. return `${this.x} ${this.y}`;
  302. }
  303. }
  304. exports.Point = Point;
  305. (function (Point) {
  306. function isPoint(instance) {
  307. return instance != null && instance instanceof Point;
  308. }
  309. Point.isPoint = isPoint;
  310. })(Point = exports.Point || (exports.Point = {}));
  311. (function (Point) {
  312. function isPointLike(p) {
  313. return (p != null &&
  314. typeof p === 'object' &&
  315. typeof p.x === 'number' &&
  316. typeof p.y === 'number');
  317. }
  318. Point.isPointLike = isPointLike;
  319. function isPointData(p) {
  320. return (p != null &&
  321. Array.isArray(p) &&
  322. p.length === 2 &&
  323. typeof p[0] === 'number' &&
  324. typeof p[1] === 'number');
  325. }
  326. Point.isPointData = isPointData;
  327. })(Point = exports.Point || (exports.Point = {}));
  328. (function (Point) {
  329. function create(x, y) {
  330. if (x == null || typeof x === 'number') {
  331. return new Point(x, y);
  332. }
  333. return clone(x);
  334. }
  335. Point.create = create;
  336. function clone(p) {
  337. if (Point.isPoint(p)) {
  338. return new Point(p.x, p.y);
  339. }
  340. if (Array.isArray(p)) {
  341. return new Point(p[0], p[1]);
  342. }
  343. return new Point(p.x, p.y);
  344. }
  345. Point.clone = clone;
  346. function toJSON(p) {
  347. if (Point.isPoint(p)) {
  348. return { x: p.x, y: p.y };
  349. }
  350. if (Array.isArray(p)) {
  351. return { x: p[0], y: p[1] };
  352. }
  353. return { x: p.x, y: p.y };
  354. }
  355. Point.toJSON = toJSON;
  356. /**
  357. * Returns a new Point object from the given polar coordinates.
  358. * @see http://en.wikipedia.org/wiki/Polar_coordinate_system
  359. */
  360. function fromPolar(r, rad, origin = new Point()) {
  361. let x = Math.abs(r * Math.cos(rad));
  362. let y = Math.abs(r * Math.sin(rad));
  363. const org = clone(origin);
  364. const deg = angle_1.Angle.normalize(angle_1.Angle.toDeg(rad));
  365. if (deg < 90) {
  366. y = -y;
  367. }
  368. else if (deg < 180) {
  369. x = -x;
  370. y = -y;
  371. }
  372. else if (deg < 270) {
  373. x = -x;
  374. }
  375. return new Point(org.x + x, org.y + y);
  376. }
  377. Point.fromPolar = fromPolar;
  378. /**
  379. * Converts rectangular to polar coordinates.
  380. */
  381. function toPolar(point, origin = new Point()) {
  382. const p = clone(point);
  383. const o = clone(origin);
  384. const dx = p.x - o.x;
  385. const dy = p.y - o.y;
  386. return new Point(Math.sqrt(dx * dx + dy * dy), // r
  387. angle_1.Angle.toRad(o.theta(p)));
  388. }
  389. Point.toPolar = toPolar;
  390. function equals(p1, p2) {
  391. if (p1 === p2) {
  392. return true;
  393. }
  394. if (p1 != null && p2 != null) {
  395. return p1.x === p2.x && p1.y === p2.y;
  396. }
  397. return false;
  398. }
  399. Point.equals = equals;
  400. function equalPoints(p1, p2) {
  401. if ((p1 == null && p2 != null) ||
  402. (p1 != null && p2 == null) ||
  403. (p1 != null && p2 != null && p1.length !== p2.length)) {
  404. return false;
  405. }
  406. if (p1 != null && p2 != null) {
  407. for (let i = 0, ii = p1.length; i < ii; i += 1) {
  408. if (!equals(p1[i], p2[i])) {
  409. return false;
  410. }
  411. }
  412. }
  413. return true;
  414. }
  415. Point.equalPoints = equalPoints;
  416. /**
  417. * Returns a point with random coordinates that fall within the range
  418. * `[x1, x2]` and `[y1, y2]`.
  419. */
  420. function random(x1, x2, y1, y2) {
  421. return new Point(util_1.GeometryUtil.random(x1, x2), util_1.GeometryUtil.random(y1, y2));
  422. }
  423. Point.random = random;
  424. function rotate(point, angle, center) {
  425. const rad = angle_1.Angle.toRad(angle_1.Angle.normalize(-angle));
  426. const sin = Math.sin(rad);
  427. const cos = Math.cos(rad);
  428. return rotateEx(point, cos, sin, center);
  429. }
  430. Point.rotate = rotate;
  431. function rotateEx(point, cos, sin, center = new Point()) {
  432. const source = clone(point);
  433. const origin = clone(center);
  434. const dx = source.x - origin.x;
  435. const dy = source.y - origin.y;
  436. const x1 = dx * cos - dy * sin;
  437. const y1 = dy * cos + dx * sin;
  438. return new Point(x1 + origin.x, y1 + origin.y);
  439. }
  440. Point.rotateEx = rotateEx;
  441. })(Point = exports.Point || (exports.Point = {}));
  442. //# sourceMappingURL=point.js.map