ConvexHull.js 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. import TreeSet from '../../../../java/util/TreeSet'
  2. import CoordinateList from '../geom/CoordinateList'
  3. import Arrays from '../../../../java/util/Arrays'
  4. import PointLocation from './PointLocation'
  5. import Stack from '../../../../java/util/Stack'
  6. import Orientation from './Orientation'
  7. import CoordinateArrays from '../geom/CoordinateArrays'
  8. import ArrayList from '../../../../java/util/ArrayList'
  9. import Comparator from '../../../../java/util/Comparator'
  10. import UniqueCoordinateArrayFilter from '../util/UniqueCoordinateArrayFilter'
  11. import Assert from '../util/Assert'
  12. export default class ConvexHull {
  13. constructor() {
  14. ConvexHull.constructor_.apply(this, arguments)
  15. }
  16. static constructor_() {
  17. this._geomFactory = null
  18. this._inputPts = null
  19. if (arguments.length === 1) {
  20. const geometry = arguments[0]
  21. ConvexHull.constructor_.call(this, ConvexHull.extractCoordinates(geometry), geometry.getFactory())
  22. } else if (arguments.length === 2) {
  23. const pts = arguments[0], geomFactory = arguments[1]
  24. this._inputPts = UniqueCoordinateArrayFilter.filterCoordinates(pts)
  25. this._geomFactory = geomFactory
  26. }
  27. }
  28. static extractCoordinates(geom) {
  29. const filter = new UniqueCoordinateArrayFilter()
  30. geom.apply(filter)
  31. return filter.getCoordinates()
  32. }
  33. preSort(pts) {
  34. let t = null
  35. for (let i = 1; i < pts.length; i++)
  36. if (pts[i].y < pts[0].y || pts[i].y === pts[0].y && pts[i].x < pts[0].x) {
  37. t = pts[0]
  38. pts[0] = pts[i]
  39. pts[i] = t
  40. }
  41. Arrays.sort(pts, 1, pts.length, new RadialComparator(pts[0]))
  42. return pts
  43. }
  44. computeOctRing(inputPts) {
  45. const octPts = this.computeOctPts(inputPts)
  46. const coordList = new CoordinateList()
  47. coordList.add(octPts, false)
  48. if (coordList.size() < 3)
  49. return null
  50. coordList.closeRing()
  51. return coordList.toCoordinateArray()
  52. }
  53. lineOrPolygon(coordinates) {
  54. coordinates = this.cleanRing(coordinates)
  55. if (coordinates.length === 3)
  56. return this._geomFactory.createLineString([coordinates[0], coordinates[1]])
  57. const linearRing = this._geomFactory.createLinearRing(coordinates)
  58. return this._geomFactory.createPolygon(linearRing)
  59. }
  60. cleanRing(original) {
  61. Assert.equals(original[0], original[original.length - 1])
  62. const cleanedRing = new ArrayList()
  63. let previousDistinctCoordinate = null
  64. for (let i = 0; i <= original.length - 2; i++) {
  65. const currentCoordinate = original[i]
  66. const nextCoordinate = original[i + 1]
  67. if (currentCoordinate.equals(nextCoordinate))
  68. continue
  69. if (previousDistinctCoordinate !== null && this.isBetween(previousDistinctCoordinate, currentCoordinate, nextCoordinate))
  70. continue
  71. cleanedRing.add(currentCoordinate)
  72. previousDistinctCoordinate = currentCoordinate
  73. }
  74. cleanedRing.add(original[original.length - 1])
  75. const cleanedRingCoordinates = new Array(cleanedRing.size()).fill(null)
  76. return cleanedRing.toArray(cleanedRingCoordinates)
  77. }
  78. isBetween(c1, c2, c3) {
  79. if (Orientation.index(c1, c2, c3) !== 0)
  80. return false
  81. if (c1.x !== c3.x) {
  82. if (c1.x <= c2.x && c2.x <= c3.x)
  83. return true
  84. if (c3.x <= c2.x && c2.x <= c1.x)
  85. return true
  86. }
  87. if (c1.y !== c3.y) {
  88. if (c1.y <= c2.y && c2.y <= c3.y)
  89. return true
  90. if (c3.y <= c2.y && c2.y <= c1.y)
  91. return true
  92. }
  93. return false
  94. }
  95. reduce(inputPts) {
  96. const polyPts = this.computeOctRing(inputPts)
  97. if (polyPts === null) return inputPts
  98. const reducedSet = new TreeSet()
  99. for (let i = 0; i < polyPts.length; i++)
  100. reducedSet.add(polyPts[i])
  101. for (let i = 0; i < inputPts.length; i++)
  102. if (!PointLocation.isInRing(inputPts[i], polyPts))
  103. reducedSet.add(inputPts[i])
  104. const reducedPts = CoordinateArrays.toCoordinateArray(reducedSet)
  105. if (reducedPts.length < 3) return this.padArray3(reducedPts)
  106. return reducedPts
  107. }
  108. getConvexHull() {
  109. if (this._inputPts.length === 0)
  110. return this._geomFactory.createGeometryCollection()
  111. if (this._inputPts.length === 1)
  112. return this._geomFactory.createPoint(this._inputPts[0])
  113. if (this._inputPts.length === 2)
  114. return this._geomFactory.createLineString(this._inputPts)
  115. let reducedPts = this._inputPts
  116. if (this._inputPts.length > 50)
  117. reducedPts = this.reduce(this._inputPts)
  118. const sortedPts = this.preSort(reducedPts)
  119. const cHS = this.grahamScan(sortedPts)
  120. const cH = this.toCoordinateArray(cHS)
  121. return this.lineOrPolygon(cH)
  122. }
  123. padArray3(pts) {
  124. const pad = new Array(3).fill(null)
  125. for (let i = 0; i < pad.length; i++)
  126. if (i < pts.length)
  127. pad[i] = pts[i]
  128. else pad[i] = pts[0]
  129. return pad
  130. }
  131. computeOctPts(inputPts) {
  132. const pts = new Array(8).fill(null)
  133. for (let j = 0; j < pts.length; j++)
  134. pts[j] = inputPts[0]
  135. for (let i = 1; i < inputPts.length; i++) {
  136. if (inputPts[i].x < pts[0].x)
  137. pts[0] = inputPts[i]
  138. if (inputPts[i].x - inputPts[i].y < pts[1].x - pts[1].y)
  139. pts[1] = inputPts[i]
  140. if (inputPts[i].y > pts[2].y)
  141. pts[2] = inputPts[i]
  142. if (inputPts[i].x + inputPts[i].y > pts[3].x + pts[3].y)
  143. pts[3] = inputPts[i]
  144. if (inputPts[i].x > pts[4].x)
  145. pts[4] = inputPts[i]
  146. if (inputPts[i].x - inputPts[i].y > pts[5].x - pts[5].y)
  147. pts[5] = inputPts[i]
  148. if (inputPts[i].y < pts[6].y)
  149. pts[6] = inputPts[i]
  150. if (inputPts[i].x + inputPts[i].y < pts[7].x + pts[7].y)
  151. pts[7] = inputPts[i]
  152. }
  153. return pts
  154. }
  155. toCoordinateArray(stack) {
  156. const coordinates = new Array(stack.size()).fill(null)
  157. for (let i = 0; i < stack.size(); i++) {
  158. const coordinate = stack.get(i)
  159. coordinates[i] = coordinate
  160. }
  161. return coordinates
  162. }
  163. grahamScan(c) {
  164. let p = null
  165. const ps = new Stack()
  166. ps.push(c[0])
  167. ps.push(c[1])
  168. ps.push(c[2])
  169. for (let i = 3; i < c.length; i++) {
  170. p = ps.pop()
  171. while (!ps.empty() && Orientation.index(ps.peek(), p, c[i]) > 0)
  172. p = ps.pop()
  173. ps.push(p)
  174. ps.push(c[i])
  175. }
  176. ps.push(c[0])
  177. return ps
  178. }
  179. }
  180. class RadialComparator {
  181. constructor() {
  182. RadialComparator.constructor_.apply(this, arguments)
  183. }
  184. static constructor_() {
  185. this._origin = null
  186. const origin = arguments[0]
  187. this._origin = origin
  188. }
  189. static polarCompare(o, p, q) {
  190. const dxp = p.x - o.x
  191. const dyp = p.y - o.y
  192. const dxq = q.x - o.x
  193. const dyq = q.y - o.y
  194. const orient = Orientation.index(o, p, q)
  195. if (orient === Orientation.COUNTERCLOCKWISE) return 1
  196. if (orient === Orientation.CLOCKWISE) return -1
  197. const op = dxp * dxp + dyp * dyp
  198. const oq = dxq * dxq + dyq * dyq
  199. if (op < oq)
  200. return -1
  201. if (op > oq)
  202. return 1
  203. return 0
  204. }
  205. compare(o1, o2) {
  206. const p1 = o1
  207. const p2 = o2
  208. return RadialComparator.polarCompare(this._origin, p1, p2)
  209. }
  210. get interfaces_() {
  211. return [Comparator]
  212. }
  213. }
  214. ConvexHull.RadialComparator = RadialComparator