LineSequencer.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. import TreeSet from '../../../../../java/util/TreeSet'
  2. import LineString from '../../geom/LineString'
  3. import Geometry from '../../geom/Geometry'
  4. import hasInterface from '../../../../../hasInterface'
  5. import GeometryFactory from '../../geom/GeometryFactory'
  6. import Collection from '../../../../../java/util/Collection'
  7. import Coordinate from '../../geom/Coordinate'
  8. import Integer from '../../../../../java/lang/Integer'
  9. import LineMergeGraph from './LineMergeGraph'
  10. import LinkedList from '../../../../../java/util/LinkedList'
  11. import GeometryComponentFilter from '../../geom/GeometryComponentFilter'
  12. import ArrayList from '../../../../../java/util/ArrayList'
  13. import ConnectedSubgraphFinder from '../../planargraph/algorithm/ConnectedSubgraphFinder'
  14. import Assert from '../../util/Assert'
  15. import MultiLineString from '../../geom/MultiLineString'
  16. import GraphComponent from '../../planargraph/GraphComponent'
  17. export default class LineSequencer {
  18. constructor() {
  19. LineSequencer.constructor_.apply(this, arguments)
  20. }
  21. static constructor_() {
  22. this._graph = new LineMergeGraph()
  23. this._factory = new GeometryFactory()
  24. this._lineCount = 0
  25. this._isRun = false
  26. this._sequencedGeometry = null
  27. this._isSequenceable = false
  28. }
  29. static findUnvisitedBestOrientedDE(node) {
  30. let wellOrientedDE = null
  31. let unvisitedDE = null
  32. for (let i = node.getOutEdges().iterator(); i.hasNext(); ) {
  33. const de = i.next()
  34. if (!de.getEdge().isVisited()) {
  35. unvisitedDE = de
  36. if (de.getEdgeDirection()) wellOrientedDE = de
  37. }
  38. }
  39. if (wellOrientedDE !== null) return wellOrientedDE
  40. return unvisitedDE
  41. }
  42. static findLowestDegreeNode(graph) {
  43. let minDegree = Integer.MAX_VALUE
  44. let minDegreeNode = null
  45. for (let i = graph.nodeIterator(); i.hasNext(); ) {
  46. const node = i.next()
  47. if (minDegreeNode === null || node.getDegree() < minDegree) {
  48. minDegree = node.getDegree()
  49. minDegreeNode = node
  50. }
  51. }
  52. return minDegreeNode
  53. }
  54. static isSequenced(geom) {
  55. if (!(geom instanceof MultiLineString))
  56. return true
  57. const mls = geom
  58. const prevSubgraphNodes = new TreeSet()
  59. let lastNode = null
  60. const currNodes = new ArrayList()
  61. for (let i = 0; i < mls.getNumGeometries(); i++) {
  62. const line = mls.getGeometryN(i)
  63. const startNode = line.getCoordinateN(0)
  64. const endNode = line.getCoordinateN(line.getNumPoints() - 1)
  65. if (prevSubgraphNodes.contains(startNode)) return false
  66. if (prevSubgraphNodes.contains(endNode)) return false
  67. if (lastNode !== null)
  68. if (!startNode.equals(lastNode)) {
  69. prevSubgraphNodes.addAll(currNodes)
  70. currNodes.clear()
  71. }
  72. currNodes.add(startNode)
  73. currNodes.add(endNode)
  74. lastNode = endNode
  75. }
  76. return true
  77. }
  78. static reverse(line) {
  79. const pts = line.getCoordinates()
  80. const revPts = new Array(pts.length).fill(null)
  81. const len = pts.length
  82. for (let i = 0; i < len; i++)
  83. revPts[len - 1 - i] = new Coordinate(pts[i])
  84. return line.getFactory().createLineString(revPts)
  85. }
  86. static sequence(geom) {
  87. const sequencer = new LineSequencer()
  88. sequencer.add(geom)
  89. return sequencer.getSequencedLineStrings()
  90. }
  91. addLine(lineString) {
  92. if (this._factory === null)
  93. this._factory = lineString.getFactory()
  94. this._graph.addEdge(lineString)
  95. this._lineCount++
  96. }
  97. hasSequence(graph) {
  98. let oddDegreeCount = 0
  99. for (let i = graph.nodeIterator(); i.hasNext(); ) {
  100. const node = i.next()
  101. if (node.getDegree() % 2 === 1) oddDegreeCount++
  102. }
  103. return oddDegreeCount <= 2
  104. }
  105. computeSequence() {
  106. if (this._isRun)
  107. return null
  108. this._isRun = true
  109. const sequences = this.findSequences()
  110. if (sequences === null) return null
  111. this._sequencedGeometry = this.buildSequencedGeometry(sequences)
  112. this._isSequenceable = true
  113. const finalLineCount = this._sequencedGeometry.getNumGeometries()
  114. Assert.isTrue(this._lineCount === finalLineCount, 'Lines were missing from result')
  115. Assert.isTrue(this._sequencedGeometry instanceof LineString || this._sequencedGeometry instanceof MultiLineString, 'Result is not lineal')
  116. }
  117. findSequences() {
  118. const sequences = new ArrayList()
  119. const csFinder = new ConnectedSubgraphFinder(this._graph)
  120. const subgraphs = csFinder.getConnectedSubgraphs()
  121. for (let i = subgraphs.iterator(); i.hasNext(); ) {
  122. const subgraph = i.next()
  123. if (this.hasSequence(subgraph)) {
  124. const seq = this.findSequence(subgraph)
  125. sequences.add(seq)
  126. } else {
  127. return null
  128. }
  129. }
  130. return sequences
  131. }
  132. addReverseSubpath(de, lit, expectedClosed) {
  133. const endNode = de.getToNode()
  134. let fromNode = null
  135. while (true) {
  136. lit.add(de.getSym())
  137. de.getEdge().setVisited(true)
  138. fromNode = de.getFromNode()
  139. const unvisitedOutDE = LineSequencer.findUnvisitedBestOrientedDE(fromNode)
  140. if (unvisitedOutDE === null) break
  141. de = unvisitedOutDE.getSym()
  142. }
  143. if (expectedClosed)
  144. Assert.isTrue(fromNode === endNode, 'path not contiguous')
  145. }
  146. findSequence(graph) {
  147. GraphComponent.setVisited(graph.edgeIterator(), false)
  148. const startNode = LineSequencer.findLowestDegreeNode(graph)
  149. const startDE = startNode.getOutEdges().iterator().next()
  150. const startDESym = startDE.getSym()
  151. const seq = new LinkedList()
  152. const lit = seq.listIterator()
  153. this.addReverseSubpath(startDESym, lit, false)
  154. while (lit.hasPrevious()) {
  155. const prev = lit.previous()
  156. const unvisitedOutDE = LineSequencer.findUnvisitedBestOrientedDE(prev.getFromNode())
  157. if (unvisitedOutDE !== null) this.addReverseSubpath(unvisitedOutDE.getSym(), lit, true)
  158. }
  159. const orientedSeq = this.orient(seq)
  160. return orientedSeq
  161. }
  162. reverse(seq) {
  163. const newSeq = new LinkedList()
  164. for (let i = seq.iterator(); i.hasNext(); ) {
  165. const de = i.next()
  166. newSeq.addFirst(de.getSym())
  167. }
  168. return newSeq
  169. }
  170. orient(seq) {
  171. const startEdge = seq.get(0)
  172. const endEdge = seq.get(seq.size() - 1)
  173. const startNode = startEdge.getFromNode()
  174. const endNode = endEdge.getToNode()
  175. let flipSeq = false
  176. const hasDegree1Node = startNode.getDegree() === 1 || endNode.getDegree() === 1
  177. if (hasDegree1Node) {
  178. let hasObviousStartNode = false
  179. if (endEdge.getToNode().getDegree() === 1 && endEdge.getEdgeDirection() === false) {
  180. hasObviousStartNode = true
  181. flipSeq = true
  182. }
  183. if (startEdge.getFromNode().getDegree() === 1 && startEdge.getEdgeDirection() === true) {
  184. hasObviousStartNode = true
  185. flipSeq = false
  186. }
  187. if (!hasObviousStartNode)
  188. if (startEdge.getFromNode().getDegree() === 1) flipSeq = true
  189. }
  190. if (flipSeq) return this.reverse(seq)
  191. return seq
  192. }
  193. buildSequencedGeometry(sequences) {
  194. const lines = new ArrayList()
  195. for (let i1 = sequences.iterator(); i1.hasNext(); ) {
  196. const seq = i1.next()
  197. for (let i2 = seq.iterator(); i2.hasNext(); ) {
  198. const de = i2.next()
  199. const e = de.getEdge()
  200. const line = e.getLine()
  201. let lineToAdd = line
  202. if (!de.getEdgeDirection() && !line.isClosed()) lineToAdd = LineSequencer.reverse(line)
  203. lines.add(lineToAdd)
  204. }
  205. }
  206. if (lines.size() === 0) return this._factory.createMultiLineString(new Array(0).fill(null))
  207. return this._factory.buildGeometry(lines)
  208. }
  209. getSequencedLineStrings() {
  210. this.computeSequence()
  211. return this._sequencedGeometry
  212. }
  213. isSequenceable() {
  214. this.computeSequence()
  215. return this._isSequenceable
  216. }
  217. add() {
  218. if (hasInterface(arguments[0], Collection)) {
  219. const geometries = arguments[0]
  220. for (let i = geometries.iterator(); i.hasNext(); ) {
  221. const geometry = i.next()
  222. this.add(geometry)
  223. }
  224. } else if (arguments[0] instanceof Geometry) {
  225. const geometry = arguments[0]
  226. geometry.apply(new (class {
  227. get interfaces_() {
  228. return [GeometryComponentFilter]
  229. }
  230. filter(component) {
  231. if (component instanceof LineString)
  232. this.addLine(component)
  233. }
  234. })())
  235. }
  236. }
  237. }