PageRenderTime 49ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/src/ol/geom/Polygon.js

https://github.com/openlayers/openlayers
JavaScript | 519 lines | 304 code | 32 blank | 183 comment | 24 complexity | fe7ad7516e03737466785a5833229357 MD5 | raw file
  1. /**
  2. * @module ol/geom/Polygon
  3. */
  4. import GeometryLayout from './GeometryLayout.js';
  5. import GeometryType from './GeometryType.js';
  6. import LinearRing from './LinearRing.js';
  7. import Point from './Point.js';
  8. import SimpleGeometry from './SimpleGeometry.js';
  9. import {arrayMaxSquaredDelta, assignClosestArrayPoint} from './flat/closest.js';
  10. import {closestSquaredDistanceXY, getCenter} from '../extent.js';
  11. import {deflateCoordinatesArray} from './flat/deflate.js';
  12. import {extend} from '../array.js';
  13. import {getInteriorPointOfArray} from './flat/interiorpoint.js';
  14. import {inflateCoordinatesArray} from './flat/inflate.js';
  15. import {intersectsLinearRingArray} from './flat/intersectsextent.js';
  16. import {linearRingsAreOriented, orientLinearRings} from './flat/orient.js';
  17. import {linearRings as linearRingsArea} from './flat/area.js';
  18. import {linearRingsContainsXY} from './flat/contains.js';
  19. import {modulo} from '../math.js';
  20. import {quantizeArray} from './flat/simplify.js';
  21. import {offset as sphereOffset} from '../sphere.js';
  22. /**
  23. * @classdesc
  24. * Polygon geometry.
  25. *
  26. * @api
  27. */
  28. class Polygon extends SimpleGeometry {
  29. /**
  30. * @param {!Array<Array<import("../coordinate.js").Coordinate>>|!Array<number>} coordinates
  31. * Array of linear rings that define the polygon. The first linear ring of the
  32. * array defines the outer-boundary or surface of the polygon. Each subsequent
  33. * linear ring defines a hole in the surface of the polygon. A linear ring is
  34. * an array of vertices' coordinates where the first coordinate and the last are
  35. * equivalent. (For internal use, flat coordinates in combination with
  36. * `opt_layout` and `opt_ends` are also accepted.)
  37. * @param {import("./GeometryLayout.js").default} [opt_layout] Layout.
  38. * @param {Array<number>} [opt_ends] Ends (for internal use with flat coordinates).
  39. */
  40. constructor(coordinates, opt_layout, opt_ends) {
  41. super();
  42. /**
  43. * @type {Array<number>}
  44. * @private
  45. */
  46. this.ends_ = [];
  47. /**
  48. * @private
  49. * @type {number}
  50. */
  51. this.flatInteriorPointRevision_ = -1;
  52. /**
  53. * @private
  54. * @type {import("../coordinate.js").Coordinate}
  55. */
  56. this.flatInteriorPoint_ = null;
  57. /**
  58. * @private
  59. * @type {number}
  60. */
  61. this.maxDelta_ = -1;
  62. /**
  63. * @private
  64. * @type {number}
  65. */
  66. this.maxDeltaRevision_ = -1;
  67. /**
  68. * @private
  69. * @type {number}
  70. */
  71. this.orientedRevision_ = -1;
  72. /**
  73. * @private
  74. * @type {Array<number>}
  75. */
  76. this.orientedFlatCoordinates_ = null;
  77. if (opt_layout !== undefined && opt_ends) {
  78. this.setFlatCoordinates(
  79. opt_layout,
  80. /** @type {Array<number>} */ (coordinates)
  81. );
  82. this.ends_ = opt_ends;
  83. } else {
  84. this.setCoordinates(
  85. /** @type {Array<Array<import("../coordinate.js").Coordinate>>} */ (
  86. coordinates
  87. ),
  88. opt_layout
  89. );
  90. }
  91. }
  92. /**
  93. * Append the passed linear ring to this polygon.
  94. * @param {LinearRing} linearRing Linear ring.
  95. * @api
  96. */
  97. appendLinearRing(linearRing) {
  98. if (!this.flatCoordinates) {
  99. this.flatCoordinates = linearRing.getFlatCoordinates().slice();
  100. } else {
  101. extend(this.flatCoordinates, linearRing.getFlatCoordinates());
  102. }
  103. this.ends_.push(this.flatCoordinates.length);
  104. this.changed();
  105. }
  106. /**
  107. * Make a complete copy of the geometry.
  108. * @return {!Polygon} Clone.
  109. * @api
  110. */
  111. clone() {
  112. const polygon = new Polygon(
  113. this.flatCoordinates.slice(),
  114. this.layout,
  115. this.ends_.slice()
  116. );
  117. polygon.applyProperties(this);
  118. return polygon;
  119. }
  120. /**
  121. * @param {number} x X.
  122. * @param {number} y Y.
  123. * @param {import("../coordinate.js").Coordinate} closestPoint Closest point.
  124. * @param {number} minSquaredDistance Minimum squared distance.
  125. * @return {number} Minimum squared distance.
  126. */
  127. closestPointXY(x, y, closestPoint, minSquaredDistance) {
  128. if (minSquaredDistance < closestSquaredDistanceXY(this.getExtent(), x, y)) {
  129. return minSquaredDistance;
  130. }
  131. if (this.maxDeltaRevision_ != this.getRevision()) {
  132. this.maxDelta_ = Math.sqrt(
  133. arrayMaxSquaredDelta(
  134. this.flatCoordinates,
  135. 0,
  136. this.ends_,
  137. this.stride,
  138. 0
  139. )
  140. );
  141. this.maxDeltaRevision_ = this.getRevision();
  142. }
  143. return assignClosestArrayPoint(
  144. this.flatCoordinates,
  145. 0,
  146. this.ends_,
  147. this.stride,
  148. this.maxDelta_,
  149. true,
  150. x,
  151. y,
  152. closestPoint,
  153. minSquaredDistance
  154. );
  155. }
  156. /**
  157. * @param {number} x X.
  158. * @param {number} y Y.
  159. * @return {boolean} Contains (x, y).
  160. */
  161. containsXY(x, y) {
  162. return linearRingsContainsXY(
  163. this.getOrientedFlatCoordinates(),
  164. 0,
  165. this.ends_,
  166. this.stride,
  167. x,
  168. y
  169. );
  170. }
  171. /**
  172. * Return the area of the polygon on projected plane.
  173. * @return {number} Area (on projected plane).
  174. * @api
  175. */
  176. getArea() {
  177. return linearRingsArea(
  178. this.getOrientedFlatCoordinates(),
  179. 0,
  180. this.ends_,
  181. this.stride
  182. );
  183. }
  184. /**
  185. * Get the coordinate array for this geometry. This array has the structure
  186. * of a GeoJSON coordinate array for polygons.
  187. *
  188. * @param {boolean} [opt_right] Orient coordinates according to the right-hand
  189. * rule (counter-clockwise for exterior and clockwise for interior rings).
  190. * If `false`, coordinates will be oriented according to the left-hand rule
  191. * (clockwise for exterior and counter-clockwise for interior rings).
  192. * By default, coordinate orientation will depend on how the geometry was
  193. * constructed.
  194. * @return {Array<Array<import("../coordinate.js").Coordinate>>} Coordinates.
  195. * @api
  196. */
  197. getCoordinates(opt_right) {
  198. let flatCoordinates;
  199. if (opt_right !== undefined) {
  200. flatCoordinates = this.getOrientedFlatCoordinates().slice();
  201. orientLinearRings(flatCoordinates, 0, this.ends_, this.stride, opt_right);
  202. } else {
  203. flatCoordinates = this.flatCoordinates;
  204. }
  205. return inflateCoordinatesArray(flatCoordinates, 0, this.ends_, this.stride);
  206. }
  207. /**
  208. * @return {Array<number>} Ends.
  209. */
  210. getEnds() {
  211. return this.ends_;
  212. }
  213. /**
  214. * @return {Array<number>} Interior point.
  215. */
  216. getFlatInteriorPoint() {
  217. if (this.flatInteriorPointRevision_ != this.getRevision()) {
  218. const flatCenter = getCenter(this.getExtent());
  219. this.flatInteriorPoint_ = getInteriorPointOfArray(
  220. this.getOrientedFlatCoordinates(),
  221. 0,
  222. this.ends_,
  223. this.stride,
  224. flatCenter,
  225. 0
  226. );
  227. this.flatInteriorPointRevision_ = this.getRevision();
  228. }
  229. return this.flatInteriorPoint_;
  230. }
  231. /**
  232. * Return an interior point of the polygon.
  233. * @return {Point} Interior point as XYM coordinate, where M is the
  234. * length of the horizontal intersection that the point belongs to.
  235. * @api
  236. */
  237. getInteriorPoint() {
  238. return new Point(this.getFlatInteriorPoint(), GeometryLayout.XYM);
  239. }
  240. /**
  241. * Return the number of rings of the polygon, this includes the exterior
  242. * ring and any interior rings.
  243. *
  244. * @return {number} Number of rings.
  245. * @api
  246. */
  247. getLinearRingCount() {
  248. return this.ends_.length;
  249. }
  250. /**
  251. * Return the Nth linear ring of the polygon geometry. Return `null` if the
  252. * given index is out of range.
  253. * The exterior linear ring is available at index `0` and the interior rings
  254. * at index `1` and beyond.
  255. *
  256. * @param {number} index Index.
  257. * @return {LinearRing|null} Linear ring.
  258. * @api
  259. */
  260. getLinearRing(index) {
  261. if (index < 0 || this.ends_.length <= index) {
  262. return null;
  263. }
  264. return new LinearRing(
  265. this.flatCoordinates.slice(
  266. index === 0 ? 0 : this.ends_[index - 1],
  267. this.ends_[index]
  268. ),
  269. this.layout
  270. );
  271. }
  272. /**
  273. * Return the linear rings of the polygon.
  274. * @return {Array<LinearRing>} Linear rings.
  275. * @api
  276. */
  277. getLinearRings() {
  278. const layout = this.layout;
  279. const flatCoordinates = this.flatCoordinates;
  280. const ends = this.ends_;
  281. const linearRings = [];
  282. let offset = 0;
  283. for (let i = 0, ii = ends.length; i < ii; ++i) {
  284. const end = ends[i];
  285. const linearRing = new LinearRing(
  286. flatCoordinates.slice(offset, end),
  287. layout
  288. );
  289. linearRings.push(linearRing);
  290. offset = end;
  291. }
  292. return linearRings;
  293. }
  294. /**
  295. * @return {Array<number>} Oriented flat coordinates.
  296. */
  297. getOrientedFlatCoordinates() {
  298. if (this.orientedRevision_ != this.getRevision()) {
  299. const flatCoordinates = this.flatCoordinates;
  300. if (linearRingsAreOriented(flatCoordinates, 0, this.ends_, this.stride)) {
  301. this.orientedFlatCoordinates_ = flatCoordinates;
  302. } else {
  303. this.orientedFlatCoordinates_ = flatCoordinates.slice();
  304. this.orientedFlatCoordinates_.length = orientLinearRings(
  305. this.orientedFlatCoordinates_,
  306. 0,
  307. this.ends_,
  308. this.stride
  309. );
  310. }
  311. this.orientedRevision_ = this.getRevision();
  312. }
  313. return this.orientedFlatCoordinates_;
  314. }
  315. /**
  316. * @param {number} squaredTolerance Squared tolerance.
  317. * @return {Polygon} Simplified Polygon.
  318. * @protected
  319. */
  320. getSimplifiedGeometryInternal(squaredTolerance) {
  321. const simplifiedFlatCoordinates = [];
  322. const simplifiedEnds = [];
  323. simplifiedFlatCoordinates.length = quantizeArray(
  324. this.flatCoordinates,
  325. 0,
  326. this.ends_,
  327. this.stride,
  328. Math.sqrt(squaredTolerance),
  329. simplifiedFlatCoordinates,
  330. 0,
  331. simplifiedEnds
  332. );
  333. return new Polygon(
  334. simplifiedFlatCoordinates,
  335. GeometryLayout.XY,
  336. simplifiedEnds
  337. );
  338. }
  339. /**
  340. * Get the type of this geometry.
  341. * @return {import("./GeometryType.js").default} Geometry type.
  342. * @api
  343. */
  344. getType() {
  345. return GeometryType.POLYGON;
  346. }
  347. /**
  348. * Test if the geometry and the passed extent intersect.
  349. * @param {import("../extent.js").Extent} extent Extent.
  350. * @return {boolean} `true` if the geometry and the extent intersect.
  351. * @api
  352. */
  353. intersectsExtent(extent) {
  354. return intersectsLinearRingArray(
  355. this.getOrientedFlatCoordinates(),
  356. 0,
  357. this.ends_,
  358. this.stride,
  359. extent
  360. );
  361. }
  362. /**
  363. * Set the coordinates of the polygon.
  364. * @param {!Array<Array<import("../coordinate.js").Coordinate>>} coordinates Coordinates.
  365. * @param {import("./GeometryLayout.js").default} [opt_layout] Layout.
  366. * @api
  367. */
  368. setCoordinates(coordinates, opt_layout) {
  369. this.setLayout(opt_layout, coordinates, 2);
  370. if (!this.flatCoordinates) {
  371. this.flatCoordinates = [];
  372. }
  373. const ends = deflateCoordinatesArray(
  374. this.flatCoordinates,
  375. 0,
  376. coordinates,
  377. this.stride,
  378. this.ends_
  379. );
  380. this.flatCoordinates.length = ends.length === 0 ? 0 : ends[ends.length - 1];
  381. this.changed();
  382. }
  383. }
  384. export default Polygon;
  385. /**
  386. * Create an approximation of a circle on the surface of a sphere.
  387. * @param {import("../coordinate.js").Coordinate} center Center (`[lon, lat]` in degrees).
  388. * @param {number} radius The great-circle distance from the center to
  389. * the polygon vertices in meters.
  390. * @param {number} [opt_n] Optional number of vertices for the resulting
  391. * polygon. Default is `32`.
  392. * @param {number} [opt_sphereRadius] Optional radius for the sphere (defaults to
  393. * the Earth's mean radius using the WGS84 ellipsoid).
  394. * @return {Polygon} The "circular" polygon.
  395. * @api
  396. */
  397. export function circular(center, radius, opt_n, opt_sphereRadius) {
  398. const n = opt_n ? opt_n : 32;
  399. /** @type {Array<number>} */
  400. const flatCoordinates = [];
  401. for (let i = 0; i < n; ++i) {
  402. extend(
  403. flatCoordinates,
  404. sphereOffset(center, radius, (2 * Math.PI * i) / n, opt_sphereRadius)
  405. );
  406. }
  407. flatCoordinates.push(flatCoordinates[0], flatCoordinates[1]);
  408. return new Polygon(flatCoordinates, GeometryLayout.XY, [
  409. flatCoordinates.length,
  410. ]);
  411. }
  412. /**
  413. * Create a polygon from an extent. The layout used is `XY`.
  414. * @param {import("../extent.js").Extent} extent The extent.
  415. * @return {Polygon} The polygon.
  416. * @api
  417. */
  418. export function fromExtent(extent) {
  419. const minX = extent[0];
  420. const minY = extent[1];
  421. const maxX = extent[2];
  422. const maxY = extent[3];
  423. const flatCoordinates = [
  424. minX,
  425. minY,
  426. minX,
  427. maxY,
  428. maxX,
  429. maxY,
  430. maxX,
  431. minY,
  432. minX,
  433. minY,
  434. ];
  435. return new Polygon(flatCoordinates, GeometryLayout.XY, [
  436. flatCoordinates.length,
  437. ]);
  438. }
  439. /**
  440. * Create a regular polygon from a circle.
  441. * @param {import("./Circle.js").default} circle Circle geometry.
  442. * @param {number} [opt_sides] Number of sides of the polygon. Default is 32.
  443. * @param {number} [opt_angle] Start angle for the first vertex of the polygon in
  444. * counter-clockwise radians. 0 means East. Default is 0.
  445. * @return {Polygon} Polygon geometry.
  446. * @api
  447. */
  448. export function fromCircle(circle, opt_sides, opt_angle) {
  449. const sides = opt_sides ? opt_sides : 32;
  450. const stride = circle.getStride();
  451. const layout = circle.getLayout();
  452. const center = circle.getCenter();
  453. const arrayLength = stride * (sides + 1);
  454. const flatCoordinates = new Array(arrayLength);
  455. for (let i = 0; i < arrayLength; i += stride) {
  456. flatCoordinates[i] = 0;
  457. flatCoordinates[i + 1] = 0;
  458. for (let j = 2; j < stride; j++) {
  459. flatCoordinates[i + j] = center[j];
  460. }
  461. }
  462. const ends = [flatCoordinates.length];
  463. const polygon = new Polygon(flatCoordinates, layout, ends);
  464. makeRegular(polygon, center, circle.getRadius(), opt_angle);
  465. return polygon;
  466. }
  467. /**
  468. * Modify the coordinates of a polygon to make it a regular polygon.
  469. * @param {Polygon} polygon Polygon geometry.
  470. * @param {import("../coordinate.js").Coordinate} center Center of the regular polygon.
  471. * @param {number} radius Radius of the regular polygon.
  472. * @param {number} [opt_angle] Start angle for the first vertex of the polygon in
  473. * counter-clockwise radians. 0 means East. Default is 0.
  474. */
  475. export function makeRegular(polygon, center, radius, opt_angle) {
  476. const flatCoordinates = polygon.getFlatCoordinates();
  477. const stride = polygon.getStride();
  478. const sides = flatCoordinates.length / stride - 1;
  479. const startAngle = opt_angle ? opt_angle : 0;
  480. for (let i = 0; i <= sides; ++i) {
  481. const offset = i * stride;
  482. const angle = startAngle + (modulo(i, sides) * 2 * Math.PI) / sides;
  483. flatCoordinates[offset] = center[0] + radius * Math.cos(angle);
  484. flatCoordinates[offset + 1] = center[1] + radius * Math.sin(angle);
  485. }
  486. polygon.changed();
  487. }