ParsePolygon.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. /**
  2. * Copyright (c) 2015-present, Parse, LLC.
  3. * All rights reserved.
  4. *
  5. * This source code is licensed under the BSD-style license found in the
  6. * LICENSE file in the root directory of this source tree. An additional grant
  7. * of patent rights can be found in the PATENTS file in the same directory.
  8. *
  9. * @flow
  10. */
  11. import ParseGeoPoint from './ParseGeoPoint';
  12. /**
  13. * Creates a new Polygon with any of the following forms:<br>
  14. * <pre>
  15. * new Polygon([[0,0],[0,1],[1,1],[1,0]])
  16. * new Polygon([GeoPoint, GeoPoint, GeoPoint])
  17. * </pre>
  18. *
  19. * <p>Represents a coordinates that may be associated
  20. * with a key in a ParseObject or used as a reference point for geo queries.
  21. * This allows proximity-based queries on the key.</p>
  22. *
  23. * <p>Example:<pre>
  24. * var polygon = new Parse.Polygon([[0,0],[0,1],[1,1],[1,0]]);
  25. * var object = new Parse.Object("PlaceObject");
  26. * object.set("area", polygon);
  27. * object.save();</pre></p>
  28. * @alias Parse.Polygon
  29. */
  30. class ParsePolygon {
  31. /*:: _coordinates: Array<Array<number>>;*/
  32. /**
  33. * @param {(Number[][]|Parse.GeoPoint[])} coordinates An Array of coordinate pairs
  34. */
  35. constructor(arg1
  36. /*: Array<Array<number>> | Array<ParseGeoPoint>*/
  37. ) {
  38. this._coordinates = ParsePolygon._validate(arg1);
  39. }
  40. /**
  41. * Coordinates value for this Polygon.
  42. * Throws an exception if not valid type.
  43. * @property coordinates
  44. * @type Array
  45. */
  46. get coordinates()
  47. /*: Array<Array<number>>*/
  48. {
  49. return this._coordinates;
  50. }
  51. set coordinates(coords
  52. /*: Array<Array<number>> | Array<ParseGeoPoint>*/
  53. ) {
  54. this._coordinates = ParsePolygon._validate(coords);
  55. }
  56. /**
  57. * Returns a JSON representation of the Polygon, suitable for Parse.
  58. * @return {Object}
  59. */
  60. toJSON()
  61. /*: { __type: string; coordinates: Array<Array<number>>;}*/
  62. {
  63. ParsePolygon._validate(this._coordinates);
  64. return {
  65. __type: 'Polygon',
  66. coordinates: this._coordinates
  67. };
  68. }
  69. /**
  70. * Checks if two polygons are equal
  71. * @param {(Parse.Polygon|Object)} other
  72. * @returns {Boolean}
  73. */
  74. equals(other
  75. /*: mixed*/
  76. )
  77. /*: boolean*/
  78. {
  79. if (!(other instanceof ParsePolygon) || this.coordinates.length !== other.coordinates.length) {
  80. return false;
  81. }
  82. let isEqual = true;
  83. for (let i = 1; i < this._coordinates.length; i += 1) {
  84. if (this._coordinates[i][0] != other.coordinates[i][0] || this._coordinates[i][1] != other.coordinates[i][1]) {
  85. isEqual = false;
  86. break;
  87. }
  88. }
  89. return isEqual;
  90. }
  91. /**
  92. *
  93. * @param {Parse.GeoPoint} point
  94. * @returns {Boolean} Returns if the point is contained in the polygon
  95. */
  96. containsPoint(point
  97. /*: ParseGeoPoint*/
  98. )
  99. /*: boolean*/
  100. {
  101. let minX = this._coordinates[0][0];
  102. let maxX = this._coordinates[0][0];
  103. let minY = this._coordinates[0][1];
  104. let maxY = this._coordinates[0][1];
  105. for (let i = 1; i < this._coordinates.length; i += 1) {
  106. const p = this._coordinates[i];
  107. minX = Math.min(p[0], minX);
  108. maxX = Math.max(p[0], maxX);
  109. minY = Math.min(p[1], minY);
  110. maxY = Math.max(p[1], maxY);
  111. }
  112. const outside = point.latitude < minX || point.latitude > maxX || point.longitude < minY || point.longitude > maxY;
  113. if (outside) {
  114. return false;
  115. }
  116. let inside = false;
  117. for (let i = 0, j = this._coordinates.length - 1; i < this._coordinates.length; j = i++) {
  118. const startX = this._coordinates[i][0];
  119. const startY = this._coordinates[i][1];
  120. const endX = this._coordinates[j][0];
  121. const endY = this._coordinates[j][1];
  122. const intersect = startY > point.longitude != endY > point.longitude && point.latitude < (endX - startX) * (point.longitude - startY) / (endY - startY) + startX;
  123. if (intersect) {
  124. inside = !inside;
  125. }
  126. }
  127. return inside;
  128. }
  129. /**
  130. * Validates that the list of coordinates can form a valid polygon
  131. * @param {Array} coords the list of coordinated to validate as a polygon
  132. * @throws {TypeError}
  133. */
  134. static _validate(coords
  135. /*: Array<Array<number>> | Array<ParseGeoPoint>*/
  136. )
  137. /*: Array<Array<number>>*/
  138. {
  139. if (!Array.isArray(coords)) {
  140. throw new TypeError('Coordinates must be an Array');
  141. }
  142. if (coords.length < 3) {
  143. throw new TypeError('Polygon must have at least 3 GeoPoints or Points');
  144. }
  145. const points = [];
  146. for (let i = 0; i < coords.length; i += 1) {
  147. const coord = coords[i];
  148. let geoPoint;
  149. if (coord instanceof ParseGeoPoint) {
  150. geoPoint = coord;
  151. } else if (Array.isArray(coord) && coord.length === 2) {
  152. geoPoint = new ParseGeoPoint(coord[0], coord[1]);
  153. } else {
  154. throw new TypeError('Coordinates must be an Array of GeoPoints or Points');
  155. }
  156. points.push([geoPoint.latitude, geoPoint.longitude]);
  157. }
  158. return points;
  159. }
  160. }
  161. export default ParsePolygon;