MinimumBoundingCircle.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. import Coordinate from '../geom/Coordinate'
  2. import Double from '../../../../java/lang/Double'
  3. import CoordinateArrays from '../geom/CoordinateArrays'
  4. import Angle from './Angle'
  5. import Assert from '../util/Assert'
  6. import Triangle from '../geom/Triangle'
  7. export default class MinimumBoundingCircle {
  8. constructor() {
  9. MinimumBoundingCircle.constructor_.apply(this, arguments)
  10. }
  11. static constructor_() {
  12. this._input = null
  13. this._extremalPts = null
  14. this._centre = null
  15. this._radius = 0.0
  16. const geom = arguments[0]
  17. this._input = geom
  18. }
  19. static farthestPoints(pts) {
  20. const dist01 = pts[0].distance(pts[1])
  21. const dist12 = pts[1].distance(pts[2])
  22. const dist20 = pts[2].distance(pts[0])
  23. if (dist01 >= dist12 && dist01 >= dist20)
  24. return [pts[0], pts[1]]
  25. if (dist12 >= dist01 && dist12 >= dist20)
  26. return [pts[1], pts[2]]
  27. return [pts[2], pts[0]]
  28. }
  29. static pointWitMinAngleWithX(pts, P) {
  30. let minSin = Double.MAX_VALUE
  31. let minAngPt = null
  32. for (let i = 0; i < pts.length; i++) {
  33. const p = pts[i]
  34. if (p === P) continue
  35. const dx = p.x - P.x
  36. let dy = p.y - P.y
  37. if (dy < 0) dy = -dy
  38. const len = Math.sqrt(dx * dx + dy * dy)
  39. const sin = dy / len
  40. if (sin < minSin) {
  41. minSin = sin
  42. minAngPt = p
  43. }
  44. }
  45. return minAngPt
  46. }
  47. static lowestPoint(pts) {
  48. let min = pts[0]
  49. for (let i = 1; i < pts.length; i++)
  50. if (pts[i].y < min.y) min = pts[i]
  51. return min
  52. }
  53. static pointWithMinAngleWithSegment(pts, P, Q) {
  54. let minAng = Double.MAX_VALUE
  55. let minAngPt = null
  56. for (let i = 0; i < pts.length; i++) {
  57. const p = pts[i]
  58. if (p === P) continue
  59. if (p === Q) continue
  60. const ang = Angle.angleBetween(P, p, Q)
  61. if (ang < minAng) {
  62. minAng = ang
  63. minAngPt = p
  64. }
  65. }
  66. return minAngPt
  67. }
  68. getRadius() {
  69. this.compute()
  70. return this._radius
  71. }
  72. getDiameter() {
  73. this.compute()
  74. switch (this._extremalPts.length) {
  75. case 0:
  76. return this._input.getFactory().createLineString()
  77. case 1:
  78. return this._input.getFactory().createPoint(this._centre)
  79. }
  80. const p0 = this._extremalPts[0]
  81. const p1 = this._extremalPts[1]
  82. return this._input.getFactory().createLineString([p0, p1])
  83. }
  84. getExtremalPoints() {
  85. this.compute()
  86. return this._extremalPts
  87. }
  88. computeCirclePoints() {
  89. if (this._input.isEmpty()) {
  90. this._extremalPts = new Array(0).fill(null)
  91. return null
  92. }
  93. if (this._input.getNumPoints() === 1) {
  94. const pts = this._input.getCoordinates()
  95. this._extremalPts = [new Coordinate(pts[0])]
  96. return null
  97. }
  98. const convexHull = this._input.convexHull()
  99. const hullPts = convexHull.getCoordinates()
  100. let pts = hullPts
  101. if (hullPts[0].equals2D(hullPts[hullPts.length - 1])) {
  102. pts = new Array(hullPts.length - 1).fill(null)
  103. CoordinateArrays.copyDeep(hullPts, 0, pts, 0, hullPts.length - 1)
  104. }
  105. if (pts.length <= 2) {
  106. this._extremalPts = CoordinateArrays.copyDeep(pts)
  107. return null
  108. }
  109. let P = MinimumBoundingCircle.lowestPoint(pts)
  110. let Q = MinimumBoundingCircle.pointWitMinAngleWithX(pts, P)
  111. for (let i = 0; i < pts.length; i++) {
  112. const R = MinimumBoundingCircle.pointWithMinAngleWithSegment(pts, P, Q)
  113. if (Angle.isObtuse(P, R, Q)) {
  114. this._extremalPts = [new Coordinate(P), new Coordinate(Q)]
  115. return null
  116. }
  117. if (Angle.isObtuse(R, P, Q)) {
  118. P = R
  119. continue
  120. }
  121. if (Angle.isObtuse(R, Q, P)) {
  122. Q = R
  123. continue
  124. }
  125. this._extremalPts = [new Coordinate(P), new Coordinate(Q), new Coordinate(R)]
  126. return null
  127. }
  128. Assert.shouldNeverReachHere('Logic failure in Minimum Bounding Circle algorithm!')
  129. }
  130. compute() {
  131. if (this._extremalPts !== null) return null
  132. this.computeCirclePoints()
  133. this.computeCentre()
  134. if (this._centre !== null) this._radius = this._centre.distance(this._extremalPts[0])
  135. }
  136. getCircle() {
  137. this.compute()
  138. if (this._centre === null) return this._input.getFactory().createPolygon()
  139. const centrePoint = this._input.getFactory().createPoint(this._centre)
  140. if (this._radius === 0.0) return centrePoint
  141. return centrePoint.buffer(this._radius)
  142. }
  143. getCentre() {
  144. this.compute()
  145. return this._centre
  146. }
  147. getMaximumDiameter() {
  148. this.compute()
  149. switch (this._extremalPts.length) {
  150. case 0:
  151. return this._input.getFactory().createLineString()
  152. case 1:
  153. return this._input.getFactory().createPoint(this._centre)
  154. case 2:
  155. return this._input.getFactory().createLineString([this._extremalPts[0], this._extremalPts[1]])
  156. default:
  157. const maxDiameter = MinimumBoundingCircle.farthestPoints(this._extremalPts)
  158. return this._input.getFactory().createLineString(maxDiameter)
  159. }
  160. }
  161. computeCentre() {
  162. switch (this._extremalPts.length) {
  163. case 0:
  164. this._centre = null
  165. break
  166. case 1:
  167. this._centre = this._extremalPts[0]
  168. break
  169. case 2:
  170. this._centre = new Coordinate((this._extremalPts[0].x + this._extremalPts[1].x) / 2.0, (this._extremalPts[0].y + this._extremalPts[1].y) / 2.0)
  171. break
  172. case 3:
  173. this._centre = Triangle.circumcentre(this._extremalPts[0], this._extremalPts[1], this._extremalPts[2])
  174. break
  175. }
  176. }
  177. }