VariableBuffer.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. import BufferParameters from './BufferParameters'
  2. import CoordinateList from '../../geom/CoordinateList'
  3. import GeometryFactory from '../../geom/GeometryFactory'
  4. import Coordinate from '../../geom/Coordinate'
  5. import IllegalArgumentException from '../../../../../java/lang/IllegalArgumentException'
  6. import Double from '../../../../../java/lang/Double'
  7. import LineSegment from '../../geom/LineSegment'
  8. import ArrayList from '../../../../../java/util/ArrayList'
  9. import Angle from '../../algorithm/Angle'
  10. export default class VariableBuffer {
  11. constructor() {
  12. VariableBuffer.constructor_.apply(this, arguments)
  13. }
  14. static constructor_() {
  15. this._line = null
  16. this._distance = null
  17. this._geomFactory = null
  18. this._quadrantSegs = BufferParameters.DEFAULT_QUADRANT_SEGMENTS
  19. const line = arguments[0], distance = arguments[1]
  20. this._line = line
  21. this._distance = distance
  22. this._geomFactory = line.getFactory()
  23. if (distance.length !== this._line.getNumPoints())
  24. throw new IllegalArgumentException('Number of distances is not equal to number of vertices')
  25. }
  26. static buffer() {
  27. if (arguments.length === 2) {
  28. const line = arguments[0], distance = arguments[1]
  29. const vb = new VariableBuffer(line, distance)
  30. return vb.getResult()
  31. } else if (arguments.length === 3) {
  32. const line = arguments[0], startDistance = arguments[1], endDistance = arguments[2]
  33. const distance = VariableBuffer.interpolate(line, startDistance, endDistance)
  34. const vb = new VariableBuffer(line, distance)
  35. return vb.getResult()
  36. } else if (arguments.length === 4) {
  37. const line = arguments[0], startDistance = arguments[1], midDistance = arguments[2], endDistance = arguments[3]
  38. const distance = VariableBuffer.interpolate(line, startDistance, midDistance, endDistance)
  39. const vb = new VariableBuffer(line, distance)
  40. return vb.getResult()
  41. }
  42. }
  43. static snapTrig(x) {
  44. if (x > 1 - VariableBuffer.SNAP_TRIG_TOL) return 1
  45. if (x < -1 + VariableBuffer.SNAP_TRIG_TOL) return -1
  46. if (Math.abs(x) < VariableBuffer.SNAP_TRIG_TOL) return 0
  47. return x
  48. }
  49. static interpolate() {
  50. if (arguments.length === 3) {
  51. let line = arguments[0], startValue = arguments[1], endValue = arguments[2]
  52. startValue = Math.abs(startValue)
  53. endValue = Math.abs(endValue)
  54. const values = new Array(line.getNumPoints()).fill(null)
  55. values[0] = startValue
  56. values[values.length - 1] = endValue
  57. const totalLen = line.getLength()
  58. const pts = line.getCoordinates()
  59. let currLen = 0
  60. for (let i = 1; i < values.length - 1; i++) {
  61. const segLen = pts[i].distance(pts[i - 1])
  62. currLen += segLen
  63. const lenFrac = currLen / totalLen
  64. const delta = lenFrac * (endValue - startValue)
  65. values[i] = startValue + delta
  66. }
  67. return values
  68. } else if (arguments.length === 4) {
  69. let line = arguments[0], startValue = arguments[1], midValue = arguments[2], endValue = arguments[3]
  70. startValue = Math.abs(startValue)
  71. midValue = Math.abs(midValue)
  72. endValue = Math.abs(endValue)
  73. const values = new Array(line.getNumPoints()).fill(null)
  74. values[0] = startValue
  75. values[values.length - 1] = endValue
  76. const pts = line.getCoordinates()
  77. const lineLen = line.getLength()
  78. const midIndex = VariableBuffer.indexAtLength(pts, lineLen / 2)
  79. const delMidStart = midValue - startValue
  80. const delEndMid = endValue - midValue
  81. const lenSM = VariableBuffer.length(pts, 0, midIndex)
  82. let currLen = 0
  83. for (let i = 1; i <= midIndex; i++) {
  84. const segLen = pts[i].distance(pts[i - 1])
  85. currLen += segLen
  86. const lenFrac = currLen / lenSM
  87. const val = startValue + lenFrac * delMidStart
  88. values[i] = val
  89. }
  90. const lenME = VariableBuffer.length(pts, midIndex, pts.length - 1)
  91. currLen = 0
  92. for (let i = midIndex + 1; i < values.length - 1; i++) {
  93. const segLen = pts[i].distance(pts[i - 1])
  94. currLen += segLen
  95. const lenFrac = currLen / lenME
  96. const val = midValue + lenFrac * delEndMid
  97. values[i] = val
  98. }
  99. return values
  100. }
  101. }
  102. static indexAtLength(pts, targetLen) {
  103. let len = 0
  104. for (let i = 1; i < pts.length; i++) {
  105. len += pts[i].distance(pts[i - 1])
  106. if (len > targetLen) return i
  107. }
  108. return pts.length - 1
  109. }
  110. static length(pts, i1, i2) {
  111. let len = 0
  112. for (let i = i1 + 1; i <= i2; i++)
  113. len += pts[i].distance(pts[i - 1])
  114. return len
  115. }
  116. static outerTangent(c1, r1, c2, r2) {
  117. if (r1 > r2) {
  118. const seg = VariableBuffer.outerTangent(c2, r2, c1, r1)
  119. return new LineSegment(seg.p1, seg.p0)
  120. }
  121. const x1 = c1.getX()
  122. const y1 = c1.getY()
  123. const x2 = c2.getX()
  124. const y2 = c2.getY()
  125. const a3 = -Math.atan2(y2 - y1, x2 - x1)
  126. const dr = r2 - r1
  127. const d = Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1))
  128. const a2 = Math.asin(dr / d)
  129. if (Double.isNaN(a2)) return null
  130. const a1 = a3 - a2
  131. const aa = Math.PI / 2 - a1
  132. const x3 = x1 + r1 * Math.cos(aa)
  133. const y3 = y1 + r1 * Math.sin(aa)
  134. const x4 = x2 + r2 * Math.cos(aa)
  135. const y4 = y2 + r2 * Math.sin(aa)
  136. return new LineSegment(x3, y3, x4, y4)
  137. }
  138. static projectPolar(p, r, ang) {
  139. const x = p.getX() + r * VariableBuffer.snapTrig(Math.cos(ang))
  140. const y = p.getY() + r * VariableBuffer.snapTrig(Math.sin(ang))
  141. return new Coordinate(x, y)
  142. }
  143. capAngleIndex(ang) {
  144. const capSegAng = Math.PI / 2 / this._quadrantSegs
  145. const index = Math.trunc(ang / capSegAng)
  146. return index
  147. }
  148. circle(center, radius) {
  149. if (radius <= 0) return null
  150. const nPts = 4 * this._quadrantSegs
  151. const pts = new Array(nPts + 1).fill(null)
  152. const angInc = Math.PI / 2 / this._quadrantSegs
  153. for (let i = 0; i < nPts; i++)
  154. pts[i] = VariableBuffer.projectPolar(center, radius, i * angInc)
  155. pts[pts.length - 1] = pts[0].copy()
  156. return this._geomFactory.createPolygon(pts)
  157. }
  158. getResult() {
  159. const parts = new ArrayList()
  160. const pts = this._line.getCoordinates()
  161. for (let i = 1; i < pts.length; i++) {
  162. const dist0 = this._distance[i - 1]
  163. const dist1 = this._distance[i]
  164. if (dist0 > 0 || dist1 > 0) {
  165. const poly = this.segmentBuffer(pts[i - 1], pts[i], dist0, dist1)
  166. if (poly !== null) parts.add(poly)
  167. }
  168. }
  169. const partsGeom = this._geomFactory.createGeometryCollection(GeometryFactory.toGeometryArray(parts))
  170. const buffer = partsGeom.union()
  171. if (buffer.isEmpty())
  172. return this._geomFactory.createPolygon()
  173. return buffer
  174. }
  175. addCap(p, r, t1, t2, coords) {
  176. let angStart = Angle.angle(p, t1)
  177. const angEnd = Angle.angle(p, t2)
  178. if (angStart < angEnd) angStart += 2 * Math.PI
  179. const indexStart = this.capAngleIndex(angStart)
  180. const indexEnd = this.capAngleIndex(angEnd)
  181. for (let i = indexStart; i > indexEnd; i--) {
  182. const ang = this.capAngle(i)
  183. coords.add(VariableBuffer.projectPolar(p, r, ang))
  184. }
  185. }
  186. segmentBuffer(p0, p1, dist0, dist1) {
  187. if (dist0 > dist1)
  188. return this.segmentBuffer(p1, p0, dist1, dist0)
  189. const tangent = VariableBuffer.outerTangent(p0, dist0, p1, dist1)
  190. if (tangent === null) {
  191. let center = p0
  192. let dist = dist0
  193. if (dist1 > dist0) {
  194. center = p1
  195. dist = dist1
  196. }
  197. return this.circle(center, dist)
  198. }
  199. const t0 = tangent.getCoordinate(0)
  200. const t1 = tangent.getCoordinate(1)
  201. const seg = new LineSegment(p0, p1)
  202. const tr0 = seg.reflect(t0)
  203. const tr1 = seg.reflect(t1)
  204. const coords = new CoordinateList()
  205. coords.add(t0)
  206. coords.add(t1)
  207. this.addCap(p1, dist1, t1, tr1, coords)
  208. coords.add(tr1)
  209. coords.add(tr0)
  210. this.addCap(p0, dist0, tr0, t0, coords)
  211. coords.add(t0)
  212. const pts = coords.toCoordinateArray()
  213. const polygon = this._geomFactory.createPolygon(pts)
  214. return polygon
  215. }
  216. capAngle(index) {
  217. const capSegAng = Math.PI / 2 / this._quadrantSegs
  218. return index * capSegAng
  219. }
  220. }
  221. VariableBuffer.SNAP_TRIG_TOL = 1e-6