index.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. 'use strict';
  2. var invariant = require('@turf/invariant');
  3. var helpers = require('@turf/helpers');
  4. var calcBbox = require('@turf/bbox');
  5. var explode = require('@turf/explode');
  6. var nearestPoint = require('@turf/nearest-point');
  7. function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
  8. var calcBbox__default = /*#__PURE__*/_interopDefaultLegacy(calcBbox);
  9. var explode__default = /*#__PURE__*/_interopDefaultLegacy(explode);
  10. var nearestPoint__default = /*#__PURE__*/_interopDefaultLegacy(nearestPoint);
  11. /**
  12. * Finds the tangents of a {@link Polygon|(Multi)Polygon} from a {@link Point}.
  13. *
  14. * @name polygonTangents
  15. * @param {Coord} pt to calculate the tangent points from
  16. * @param {Feature<Polygon|MultiPolygon>} polygon to get tangents from
  17. * @returns {FeatureCollection<Point>} Feature Collection containing the two tangent points
  18. * @example
  19. * var polygon = turf.polygon([[[11, 0], [22, 4], [31, 0], [31, 11], [21, 15], [11, 11], [11, 0]]]);
  20. * var point = turf.point([61, 5]);
  21. *
  22. * var tangents = turf.polygonTangents(point, polygon)
  23. *
  24. * //addToMap
  25. * var addToMap = [tangents, point, polygon];
  26. */
  27. function polygonTangents(pt, polygon) {
  28. var pointCoords = invariant.getCoords(pt);
  29. var polyCoords = invariant.getCoords(polygon);
  30. var rtan;
  31. var ltan;
  32. var enext;
  33. var eprev;
  34. var bbox = calcBbox__default['default'](polygon);
  35. var nearestPtIndex = 0;
  36. var nearest = null;
  37. // If the point lies inside the polygon bbox then we need to be a bit trickier
  38. // otherwise points lying inside reflex angles on concave polys can have issues
  39. if (
  40. pointCoords[0] > bbox[0] &&
  41. pointCoords[0] < bbox[2] &&
  42. pointCoords[1] > bbox[1] &&
  43. pointCoords[1] < bbox[3]
  44. ) {
  45. nearest = nearestPoint__default['default'](pt, explode__default['default'](polygon));
  46. nearestPtIndex = nearest.properties.featureIndex;
  47. }
  48. var type = invariant.getType(polygon);
  49. switch (type) {
  50. case "Polygon":
  51. rtan = polyCoords[0][nearestPtIndex];
  52. ltan = polyCoords[0][0];
  53. if (nearest !== null) {
  54. if (nearest.geometry.coordinates[1] < pointCoords[1])
  55. ltan = polyCoords[0][nearestPtIndex];
  56. }
  57. eprev = isLeft(
  58. polyCoords[0][0],
  59. polyCoords[0][polyCoords[0].length - 1],
  60. pointCoords
  61. );
  62. var out = processPolygon(
  63. polyCoords[0],
  64. pointCoords,
  65. eprev,
  66. enext,
  67. rtan,
  68. ltan);
  69. rtan = out[0];
  70. ltan = out[1];
  71. break;
  72. case "MultiPolygon":
  73. var closestFeature = 0;
  74. var closestVertex = 0;
  75. var verticesCounted = 0;
  76. for (var i = 0; i < polyCoords[0].length; i++) {
  77. closestFeature = i;
  78. var verticeFound = false;
  79. for (var i2 = 0; i2 < polyCoords[0][i].length; i2++) {
  80. closestVertex = i2;
  81. if (verticesCounted === nearestPtIndex) {
  82. verticeFound = true;
  83. break;
  84. }
  85. verticesCounted++;
  86. }
  87. if (verticeFound) break;
  88. }
  89. rtan = polyCoords[0][closestFeature][closestVertex];
  90. ltan = polyCoords[0][closestFeature][closestVertex];
  91. eprev = isLeft(
  92. polyCoords[0][0][0],
  93. polyCoords[0][0][polyCoords[0][0].length - 1],
  94. pointCoords
  95. );
  96. polyCoords.forEach(function (ring) {
  97. var out = processPolygon(
  98. ring[0],
  99. pointCoords,
  100. eprev,
  101. enext,
  102. rtan,
  103. ltan);
  104. rtan = out[0];
  105. ltan = out[1];
  106. });
  107. break;
  108. }
  109. return helpers.featureCollection([helpers.point(rtan), helpers.point(ltan)]);
  110. }
  111. function processPolygon(polygonCoords, ptCoords, eprev, enext, rtan, ltan) {
  112. for (var i = 0; i < polygonCoords.length; i++) {
  113. var currentCoords = polygonCoords[i];
  114. var nextCoordPair = polygonCoords[i + 1];
  115. if (i === polygonCoords.length - 1) {
  116. nextCoordPair = polygonCoords[0];
  117. }
  118. enext = isLeft(currentCoords, nextCoordPair, ptCoords);
  119. if (eprev <= 0 && enext > 0) {
  120. if (!isBelow(ptCoords, currentCoords, rtan)) {
  121. rtan = currentCoords;
  122. }
  123. } else if (eprev > 0 && enext <= 0) {
  124. if (!isAbove(ptCoords, currentCoords, ltan)) {
  125. ltan = currentCoords;
  126. }
  127. }
  128. eprev = enext;
  129. }
  130. return [rtan, ltan];
  131. }
  132. function isAbove(point1, point2, point3) {
  133. return isLeft(point1, point2, point3) > 0;
  134. }
  135. function isBelow(point1, point2, point3) {
  136. return isLeft(point1, point2, point3) < 0;
  137. }
  138. function isLeft(point1, point2, point3) {
  139. return (
  140. (point2[0] - point1[0]) * (point3[1] - point1[1]) -
  141. (point3[0] - point1[0]) * (point2[1] - point1[1])
  142. );
  143. }
  144. module.exports = polygonTangents;
  145. module.exports.default = polygonTangents;