IsValidOp.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. import Location from '../../geom/Location'
  2. import TreeSet from '../../../../../java/util/TreeSet'
  3. import LineString from '../../geom/LineString'
  4. import Geometry from '../../geom/Geometry'
  5. import ConnectedInteriorTester from './ConnectedInteriorTester'
  6. import Coordinate from '../../geom/Coordinate'
  7. import Point from '../../geom/Point'
  8. import Polygon from '../../geom/Polygon'
  9. import MultiPoint from '../../geom/MultiPoint'
  10. import PointLocation from '../../algorithm/PointLocation'
  11. import LinearRing from '../../geom/LinearRing'
  12. import Double from '../../../../../java/lang/Double'
  13. import GeometryGraph from '../../geomgraph/GeometryGraph'
  14. import MultiPolygon from '../../geom/MultiPolygon'
  15. import ConsistentAreaTester from './ConsistentAreaTester'
  16. import GeometryCollection from '../../geom/GeometryCollection'
  17. import UnsupportedOperationException from '../../../../../java/lang/UnsupportedOperationException'
  18. import IndexedNestedRingTester from './IndexedNestedRingTester'
  19. import RobustLineIntersector from '../../algorithm/RobustLineIntersector'
  20. import TopologyValidationError from './TopologyValidationError'
  21. import IndexedPointInAreaLocator from '../../algorithm/locate/IndexedPointInAreaLocator'
  22. import Assert from '../../util/Assert'
  23. export default class IsValidOp {
  24. constructor() {
  25. IsValidOp.constructor_.apply(this, arguments)
  26. }
  27. static constructor_() {
  28. this._parentGeometry = null
  29. this._isSelfTouchingRingFormingHoleValid = false
  30. this._validErr = null
  31. const parentGeometry = arguments[0]
  32. this._parentGeometry = parentGeometry
  33. }
  34. static findPtNotNode(testCoords, searchRing, graph) {
  35. const searchEdge = graph.findEdge(searchRing)
  36. const eiList = searchEdge.getEdgeIntersectionList()
  37. for (let i = 0; i < testCoords.length; i++) {
  38. const pt = testCoords[i]
  39. if (!eiList.isIntersection(pt)) return pt
  40. }
  41. return null
  42. }
  43. static isValid() {
  44. if (arguments[0] instanceof Geometry) {
  45. const geom = arguments[0]
  46. const isValidOp = new IsValidOp(geom)
  47. return isValidOp.isValid()
  48. } else if (arguments[0] instanceof Coordinate) {
  49. const coord = arguments[0]
  50. if (Double.isNaN(coord.x)) return false
  51. if (Double.isInfinite(coord.x)) return false
  52. if (Double.isNaN(coord.y)) return false
  53. if (Double.isInfinite(coord.y)) return false
  54. return true
  55. }
  56. }
  57. checkInvalidCoordinates() {
  58. if (arguments[0] instanceof Array) {
  59. const coords = arguments[0]
  60. for (let i = 0; i < coords.length; i++)
  61. if (!IsValidOp.isValid(coords[i])) {
  62. this._validErr = new TopologyValidationError(TopologyValidationError.INVALID_COORDINATE, coords[i])
  63. return null
  64. }
  65. } else if (arguments[0] instanceof Polygon) {
  66. const poly = arguments[0]
  67. this.checkInvalidCoordinates(poly.getExteriorRing().getCoordinates())
  68. if (this._validErr !== null) return null
  69. for (let i = 0; i < poly.getNumInteriorRing(); i++) {
  70. this.checkInvalidCoordinates(poly.getInteriorRingN(i).getCoordinates())
  71. if (this._validErr !== null) return null
  72. }
  73. }
  74. }
  75. checkHolesNotNested(p, graph) {
  76. if (p.getNumInteriorRing() <= 0) return null
  77. const nestedTester = new IndexedNestedRingTester(graph)
  78. for (let i = 0; i < p.getNumInteriorRing(); i++) {
  79. const innerHole = p.getInteriorRingN(i)
  80. if (innerHole.isEmpty()) continue
  81. nestedTester.add(innerHole)
  82. }
  83. const isNonNested = nestedTester.isNonNested()
  84. if (!isNonNested)
  85. this._validErr = new TopologyValidationError(TopologyValidationError.NESTED_HOLES, nestedTester.getNestedPoint())
  86. }
  87. checkConsistentArea(graph) {
  88. const cat = new ConsistentAreaTester(graph)
  89. const isValidArea = cat.isNodeConsistentArea()
  90. if (!isValidArea) {
  91. this._validErr = new TopologyValidationError(TopologyValidationError.SELF_INTERSECTION, cat.getInvalidPoint())
  92. return null
  93. }
  94. if (cat.hasDuplicateRings())
  95. this._validErr = new TopologyValidationError(TopologyValidationError.DUPLICATE_RINGS, cat.getInvalidPoint())
  96. }
  97. isValid() {
  98. this.checkValid(this._parentGeometry)
  99. return this._validErr === null
  100. }
  101. checkShellInsideHole(shell, hole, graph) {
  102. const shellPts = shell.getCoordinates()
  103. const holePts = hole.getCoordinates()
  104. const shellPt = IsValidOp.findPtNotNode(shellPts, hole, graph)
  105. if (shellPt !== null) {
  106. const insideHole = PointLocation.isInRing(shellPt, holePts)
  107. if (!insideHole)
  108. return shellPt
  109. }
  110. const holePt = IsValidOp.findPtNotNode(holePts, shell, graph)
  111. if (holePt !== null) {
  112. const insideShell = PointLocation.isInRing(holePt, shellPts)
  113. if (insideShell)
  114. return holePt
  115. return null
  116. }
  117. Assert.shouldNeverReachHere('points in shell and hole appear to be equal')
  118. return null
  119. }
  120. checkNoSelfIntersectingRings(graph) {
  121. for (let i = graph.getEdgeIterator(); i.hasNext(); ) {
  122. const e = i.next()
  123. this.checkNoSelfIntersectingRing(e.getEdgeIntersectionList())
  124. if (this._validErr !== null) return null
  125. }
  126. }
  127. checkConnectedInteriors(graph) {
  128. const cit = new ConnectedInteriorTester(graph)
  129. if (!cit.isInteriorsConnected()) this._validErr = new TopologyValidationError(TopologyValidationError.DISCONNECTED_INTERIOR, cit.getCoordinate())
  130. }
  131. checkNoSelfIntersectingRing(eiList) {
  132. const nodeSet = new TreeSet()
  133. let isFirst = true
  134. for (let i = eiList.iterator(); i.hasNext(); ) {
  135. const ei = i.next()
  136. if (isFirst) {
  137. isFirst = false
  138. continue
  139. }
  140. if (nodeSet.contains(ei.coord)) {
  141. this._validErr = new TopologyValidationError(TopologyValidationError.RING_SELF_INTERSECTION, ei.coord)
  142. return null
  143. } else {
  144. nodeSet.add(ei.coord)
  145. }
  146. }
  147. }
  148. checkHolesInShell(p, graph) {
  149. if (p.getNumInteriorRing() <= 0) return null
  150. const shell = p.getExteriorRing()
  151. const isShellEmpty = shell.isEmpty()
  152. const pir = new IndexedPointInAreaLocator(shell)
  153. for (let i = 0; i < p.getNumInteriorRing(); i++) {
  154. const hole = p.getInteriorRingN(i)
  155. let holePt = null
  156. if (hole.isEmpty()) continue
  157. holePt = IsValidOp.findPtNotNode(hole.getCoordinates(), shell, graph)
  158. if (holePt === null) return null
  159. const outside = isShellEmpty || Location.EXTERIOR === pir.locate(holePt)
  160. if (outside) {
  161. this._validErr = new TopologyValidationError(TopologyValidationError.HOLE_OUTSIDE_SHELL, holePt)
  162. return null
  163. }
  164. }
  165. }
  166. checkTooFewPoints(graph) {
  167. if (graph.hasTooFewPoints()) {
  168. this._validErr = new TopologyValidationError(TopologyValidationError.TOO_FEW_POINTS, graph.getInvalidPoint())
  169. return null
  170. }
  171. }
  172. getValidationError() {
  173. this.checkValid(this._parentGeometry)
  174. return this._validErr
  175. }
  176. checkValid() {
  177. if (arguments[0] instanceof Point) {
  178. const g = arguments[0]
  179. this.checkInvalidCoordinates(g.getCoordinates())
  180. } else if (arguments[0] instanceof MultiPoint) {
  181. const g = arguments[0]
  182. this.checkInvalidCoordinates(g.getCoordinates())
  183. } else if (arguments[0] instanceof LinearRing) {
  184. const g = arguments[0]
  185. this.checkInvalidCoordinates(g.getCoordinates())
  186. if (this._validErr !== null) return null
  187. this.checkClosedRing(g)
  188. if (this._validErr !== null) return null
  189. const graph = new GeometryGraph(0, g)
  190. this.checkTooFewPoints(graph)
  191. if (this._validErr !== null) return null
  192. const li = new RobustLineIntersector()
  193. graph.computeSelfNodes(li, true, true)
  194. this.checkNoSelfIntersectingRings(graph)
  195. } else if (arguments[0] instanceof LineString) {
  196. const g = arguments[0]
  197. this.checkInvalidCoordinates(g.getCoordinates())
  198. if (this._validErr !== null) return null
  199. const graph = new GeometryGraph(0, g)
  200. this.checkTooFewPoints(graph)
  201. } else if (arguments[0] instanceof Polygon) {
  202. const g = arguments[0]
  203. this.checkInvalidCoordinates(g)
  204. if (this._validErr !== null) return null
  205. this.checkClosedRings(g)
  206. if (this._validErr !== null) return null
  207. const graph = new GeometryGraph(0, g)
  208. this.checkTooFewPoints(graph)
  209. if (this._validErr !== null) return null
  210. this.checkConsistentArea(graph)
  211. if (this._validErr !== null) return null
  212. if (!this._isSelfTouchingRingFormingHoleValid) {
  213. this.checkNoSelfIntersectingRings(graph)
  214. if (this._validErr !== null) return null
  215. }
  216. this.checkHolesInShell(g, graph)
  217. if (this._validErr !== null) return null
  218. this.checkHolesNotNested(g, graph)
  219. if (this._validErr !== null) return null
  220. this.checkConnectedInteriors(graph)
  221. } else if (arguments[0] instanceof MultiPolygon) {
  222. const g = arguments[0]
  223. for (let i = 0; i < g.getNumGeometries(); i++) {
  224. const p = g.getGeometryN(i)
  225. this.checkInvalidCoordinates(p)
  226. if (this._validErr !== null) return null
  227. this.checkClosedRings(p)
  228. if (this._validErr !== null) return null
  229. }
  230. const graph = new GeometryGraph(0, g)
  231. this.checkTooFewPoints(graph)
  232. if (this._validErr !== null) return null
  233. this.checkConsistentArea(graph)
  234. if (this._validErr !== null) return null
  235. if (!this._isSelfTouchingRingFormingHoleValid) {
  236. this.checkNoSelfIntersectingRings(graph)
  237. if (this._validErr !== null) return null
  238. }
  239. for (let i = 0; i < g.getNumGeometries(); i++) {
  240. const p = g.getGeometryN(i)
  241. this.checkHolesInShell(p, graph)
  242. if (this._validErr !== null) return null
  243. }
  244. for (let i = 0; i < g.getNumGeometries(); i++) {
  245. const p = g.getGeometryN(i)
  246. this.checkHolesNotNested(p, graph)
  247. if (this._validErr !== null) return null
  248. }
  249. this.checkShellsNotNested(g, graph)
  250. if (this._validErr !== null) return null
  251. this.checkConnectedInteriors(graph)
  252. } else if (arguments[0] instanceof GeometryCollection) {
  253. const gc = arguments[0]
  254. for (let i = 0; i < gc.getNumGeometries(); i++) {
  255. const g = gc.getGeometryN(i)
  256. this.checkValid(g)
  257. if (this._validErr !== null) return null
  258. }
  259. } else if (arguments[0] instanceof Geometry) {
  260. const g = arguments[0]
  261. this._validErr = null
  262. if (g.isEmpty()) return null
  263. if (g instanceof Point) this.checkValid(g); else if (g instanceof MultiPoint) this.checkValid(g); else if (g instanceof LinearRing) this.checkValid(g); else if (g instanceof LineString) this.checkValid(g); else if (g instanceof Polygon) this.checkValid(g); else if (g instanceof MultiPolygon) this.checkValid(g); else if (g instanceof GeometryCollection) this.checkValid(g); else throw new UnsupportedOperationException(g.getGeometryType())
  264. }
  265. }
  266. setSelfTouchingRingFormingHoleValid(isValid) {
  267. this._isSelfTouchingRingFormingHoleValid = isValid
  268. }
  269. checkShellNotNested(shell, p, graph) {
  270. const shellPts = shell.getCoordinates()
  271. const polyShell = p.getExteriorRing()
  272. if (polyShell.isEmpty()) return null
  273. const polyPts = polyShell.getCoordinates()
  274. const shellPt = IsValidOp.findPtNotNode(shellPts, polyShell, graph)
  275. if (shellPt === null) return null
  276. const insidePolyShell = PointLocation.isInRing(shellPt, polyPts)
  277. if (!insidePolyShell) return null
  278. if (p.getNumInteriorRing() <= 0) {
  279. this._validErr = new TopologyValidationError(TopologyValidationError.NESTED_SHELLS, shellPt)
  280. return null
  281. }
  282. let badNestedPt = null
  283. for (let i = 0; i < p.getNumInteriorRing(); i++) {
  284. const hole = p.getInteriorRingN(i)
  285. badNestedPt = this.checkShellInsideHole(shell, hole, graph)
  286. if (badNestedPt === null) return null
  287. }
  288. this._validErr = new TopologyValidationError(TopologyValidationError.NESTED_SHELLS, badNestedPt)
  289. }
  290. checkClosedRings(poly) {
  291. this.checkClosedRing(poly.getExteriorRing())
  292. if (this._validErr !== null) return null
  293. for (let i = 0; i < poly.getNumInteriorRing(); i++) {
  294. this.checkClosedRing(poly.getInteriorRingN(i))
  295. if (this._validErr !== null) return null
  296. }
  297. }
  298. checkClosedRing(ring) {
  299. if (ring.isEmpty()) return null
  300. if (!ring.isClosed()) {
  301. let pt = null
  302. if (ring.getNumPoints() >= 1) pt = ring.getCoordinateN(0)
  303. this._validErr = new TopologyValidationError(TopologyValidationError.RING_NOT_CLOSED, pt)
  304. }
  305. }
  306. checkShellsNotNested(mp, graph) {
  307. for (let i = 0; i < mp.getNumGeometries(); i++) {
  308. const p = mp.getGeometryN(i)
  309. const shell = p.getExteriorRing()
  310. for (let j = 0; j < mp.getNumGeometries(); j++) {
  311. if (i === j) continue
  312. const p2 = mp.getGeometryN(j)
  313. this.checkShellNotNested(shell, p2, graph)
  314. if (this._validErr !== null) return null
  315. }
  316. }
  317. }
  318. }