mirror of
https://github.com/bjornharrtell/jsts.git
synced 2026-02-01 14:36:46 +00:00
Remove unintended partial newer not working files from jts transpilation
This commit is contained in:
parent
ad5af47313
commit
0ae8b18d05
@ -1,47 +0,0 @@
|
||||
import Orientation from './Orientation.js'
|
||||
import Quadrant from '../geom/Quadrant.js'
|
||||
export default class PolygonNodeTopology {
|
||||
static isBetween(origin, p, e0, e1) {
|
||||
const isGreater0 = PolygonNodeTopology.isAngleGreater(origin, p, e0)
|
||||
if (!isGreater0) return false
|
||||
const isGreater1 = PolygonNodeTopology.isAngleGreater(origin, p, e1)
|
||||
return !isGreater1
|
||||
}
|
||||
static isInteriorSegment(nodePt, a0, a1, b) {
|
||||
let aLo = a0
|
||||
let aHi = a1
|
||||
let isInteriorBetween = true
|
||||
if (PolygonNodeTopology.isAngleGreater(nodePt, aLo, aHi)) {
|
||||
aLo = a1
|
||||
aHi = a0
|
||||
isInteriorBetween = false
|
||||
}
|
||||
const isBetween = PolygonNodeTopology.isBetween(nodePt, b, aLo, aHi)
|
||||
const isInterior = isBetween && isInteriorBetween || !isBetween && !isInteriorBetween
|
||||
return isInterior
|
||||
}
|
||||
static isCrossing(nodePt, a0, a1, b0, b1) {
|
||||
let aLo = a0
|
||||
let aHi = a1
|
||||
if (PolygonNodeTopology.isAngleGreater(nodePt, aLo, aHi)) {
|
||||
aLo = a1
|
||||
aHi = a0
|
||||
}
|
||||
const isBetween0 = PolygonNodeTopology.isBetween(nodePt, b0, aLo, aHi)
|
||||
const isBetween1 = PolygonNodeTopology.isBetween(nodePt, b1, aLo, aHi)
|
||||
return isBetween0 !== isBetween1
|
||||
}
|
||||
static isAngleGreater(origin, p, q) {
|
||||
const quadrantP = PolygonNodeTopology.quadrant(origin, p)
|
||||
const quadrantQ = PolygonNodeTopology.quadrant(origin, q)
|
||||
if (quadrantP > quadrantQ) return true
|
||||
if (quadrantP < quadrantQ) return false
|
||||
const orient = Orientation.index(origin, q, p)
|
||||
return orient === Orientation.COUNTERCLOCKWISE
|
||||
}
|
||||
static quadrant(origin, p) {
|
||||
const dx = p.getX() - origin.getX()
|
||||
const dy = p.getY() - origin.getY()
|
||||
return Quadrant.quadrant(dx, dy)
|
||||
}
|
||||
}
|
||||
@ -22,10 +22,10 @@ export default class RobustDeterminant {
|
||||
return sign
|
||||
|
||||
else
|
||||
if (x2 > 0)
|
||||
return sign
|
||||
else
|
||||
return -sign
|
||||
if (x2 > 0)
|
||||
return sign
|
||||
else
|
||||
return -sign
|
||||
|
||||
|
||||
|
||||
@ -37,10 +37,10 @@ export default class RobustDeterminant {
|
||||
return -sign
|
||||
|
||||
else
|
||||
if (x1 > 0)
|
||||
return -sign
|
||||
else
|
||||
return sign
|
||||
if (x1 > 0)
|
||||
return -sign
|
||||
else
|
||||
return sign
|
||||
|
||||
|
||||
|
||||
@ -58,48 +58,48 @@ export default class RobustDeterminant {
|
||||
y2 = swap
|
||||
}
|
||||
} else
|
||||
if (y1 <= -y2) {
|
||||
sign = -sign
|
||||
x2 = -x2
|
||||
y2 = -y2
|
||||
} else {
|
||||
swap = x1
|
||||
x1 = -x2
|
||||
x2 = swap
|
||||
swap = y1
|
||||
y1 = -y2
|
||||
y2 = swap
|
||||
}
|
||||
if (y1 <= -y2) {
|
||||
sign = -sign
|
||||
x2 = -x2
|
||||
y2 = -y2
|
||||
} else {
|
||||
swap = x1
|
||||
x1 = -x2
|
||||
x2 = swap
|
||||
swap = y1
|
||||
y1 = -y2
|
||||
y2 = swap
|
||||
}
|
||||
} else
|
||||
if (0.0 < y2) {
|
||||
if (-y1 <= y2) {
|
||||
sign = -sign
|
||||
x1 = -x1
|
||||
y1 = -y1
|
||||
} else {
|
||||
swap = -x1
|
||||
x1 = x2
|
||||
x2 = swap
|
||||
swap = -y1
|
||||
y1 = y2
|
||||
y2 = swap
|
||||
}
|
||||
} else
|
||||
if (y1 >= y2) {
|
||||
x1 = -x1
|
||||
y1 = -y1
|
||||
x2 = -x2
|
||||
y2 = -y2
|
||||
if (0.0 < y2) {
|
||||
if (-y1 <= y2) {
|
||||
sign = -sign
|
||||
x1 = -x1
|
||||
y1 = -y1
|
||||
} else {
|
||||
swap = -x1
|
||||
x1 = x2
|
||||
x2 = swap
|
||||
swap = -y1
|
||||
y1 = y2
|
||||
y2 = swap
|
||||
}
|
||||
} else
|
||||
if (y1 >= y2) {
|
||||
x1 = -x1
|
||||
y1 = -y1
|
||||
x2 = -x2
|
||||
y2 = -y2
|
||||
|
||||
} else {
|
||||
sign = -sign
|
||||
swap = -x1
|
||||
x1 = -x2
|
||||
x2 = swap
|
||||
swap = -y1
|
||||
y1 = -y2
|
||||
y2 = swap
|
||||
}
|
||||
} else {
|
||||
sign = -sign
|
||||
swap = -x1
|
||||
x1 = -x2
|
||||
x2 = swap
|
||||
swap = -y1
|
||||
y1 = -y2
|
||||
y2 = swap
|
||||
}
|
||||
|
||||
|
||||
if (0.0 < x1) {
|
||||
@ -112,17 +112,17 @@ export default class RobustDeterminant {
|
||||
else
|
||||
return sign
|
||||
} else
|
||||
if (0.0 < x2) {
|
||||
return -sign
|
||||
} else
|
||||
if (x1 >= x2) {
|
||||
sign = -sign
|
||||
x1 = -x1
|
||||
x2 = -x2
|
||||
if (0.0 < x2) {
|
||||
return -sign
|
||||
} else
|
||||
if (x1 >= x2) {
|
||||
sign = -sign
|
||||
x1 = -x1
|
||||
x2 = -x2
|
||||
|
||||
} else {
|
||||
return -sign
|
||||
}
|
||||
} else {
|
||||
return -sign
|
||||
}
|
||||
|
||||
|
||||
while (true) {
|
||||
|
||||
@ -32,7 +32,7 @@ export default class Quadrant {
|
||||
if (dx >= 0.0)
|
||||
if (dy >= 0.0) return Quadrant.NE; else return Quadrant.SE
|
||||
else
|
||||
if (dy >= 0.0) return Quadrant.NW; else return Quadrant.SW
|
||||
if (dy >= 0.0) return Quadrant.NW; else return Quadrant.SW
|
||||
|
||||
} else if (arguments[0] instanceof Coordinate && arguments[1] instanceof Coordinate) {
|
||||
const p0 = arguments[0], p1 = arguments[1]
|
||||
@ -40,7 +40,7 @@ export default class Quadrant {
|
||||
if (p1.x >= p0.x)
|
||||
if (p1.y >= p0.y) return Quadrant.NE; else return Quadrant.SE
|
||||
else
|
||||
if (p1.y >= p0.y) return Quadrant.NW; else return Quadrant.SW
|
||||
if (p1.y >= p0.y) return Quadrant.NW; else return Quadrant.SW
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,13 +11,13 @@ export default class Octant {
|
||||
if (dy >= 0)
|
||||
if (adx >= ady) return 0; else return 1
|
||||
else
|
||||
if (adx >= ady) return 7; else return 6
|
||||
if (adx >= ady) return 7; else return 6
|
||||
|
||||
else
|
||||
if (dy >= 0)
|
||||
if (adx >= ady) return 3; else return 2
|
||||
else
|
||||
if (adx >= ady) return 4; else return 5
|
||||
if (dy >= 0)
|
||||
if (adx >= ady) return 3; else return 2
|
||||
else
|
||||
if (adx >= ady) return 4; else return 5
|
||||
|
||||
|
||||
} else if (arguments[0] instanceof Coordinate && arguments[1] instanceof Coordinate) {
|
||||
|
||||
@ -37,10 +37,10 @@ export default class SegmentStringDissolver {
|
||||
if (existing === null) {
|
||||
this.add(oca, segString)
|
||||
} else
|
||||
if (this._merger !== null) {
|
||||
const isSameOrientation = CoordinateArrays.equals(existing.getCoordinates(), segString.getCoordinates())
|
||||
this._merger.merge(existing, segString, isSameOrientation)
|
||||
}
|
||||
if (this._merger !== null) {
|
||||
const isSameOrientation = CoordinateArrays.equals(existing.getCoordinates(), segString.getCoordinates())
|
||||
this._merger.merge(existing, segString, isSameOrientation)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,159 +0,0 @@
|
||||
import Location from '../../geom/Location.js'
|
||||
import LineString from '../../geom/LineString.js'
|
||||
import Position from '../../geom/Position.js'
|
||||
import Point from '../../geom/Point.js'
|
||||
import NodedSegmentString from '../../noding/NodedSegmentString.js'
|
||||
import Polygon from '../../geom/Polygon.js'
|
||||
import MultiPoint from '../../geom/MultiPoint.js'
|
||||
import OffsetCurveBuilder from './OffsetCurveBuilder.js'
|
||||
import LinearRing from '../../geom/LinearRing.js'
|
||||
import Orientation from '../../algorithm/Orientation.js'
|
||||
import MultiPolygon from '../../geom/MultiPolygon.js'
|
||||
import Label from '../../geomgraph/Label.js'
|
||||
import GeometryCollection from '../../geom/GeometryCollection.js'
|
||||
import UnsupportedOperationException from '../../../../../java/lang/UnsupportedOperationException.js'
|
||||
import CoordinateArrays from '../../geom/CoordinateArrays.js'
|
||||
import ArrayList from '../../../../../java/util/ArrayList.js'
|
||||
import Distance from '../../algorithm/Distance.js'
|
||||
import MultiLineString from '../../geom/MultiLineString.js'
|
||||
import Triangle from '../../geom/Triangle.js'
|
||||
export default class BufferCurveSetBuilder {
|
||||
constructor() {
|
||||
BufferCurveSetBuilder.constructor_.apply(this, arguments)
|
||||
}
|
||||
static constructor_() {
|
||||
this._inputGeom = null
|
||||
this._distance = null
|
||||
this._curveBuilder = null
|
||||
this._curveList = new ArrayList()
|
||||
this._isInvertOrientation = false
|
||||
const inputGeom = arguments[0], distance = arguments[1], precisionModel = arguments[2], bufParams = arguments[3]
|
||||
this._inputGeom = inputGeom
|
||||
this._distance = distance
|
||||
this._curveBuilder = new OffsetCurveBuilder(precisionModel, bufParams)
|
||||
}
|
||||
static clean(coords) {
|
||||
return CoordinateArrays.removeRepeatedOrInvalidPoints(coords)
|
||||
}
|
||||
static isTriangleErodedCompletely(triangleCoord, bufferDistance) {
|
||||
const tri = new Triangle(triangleCoord[0], triangleCoord[1], triangleCoord[2])
|
||||
const inCentre = tri.inCentre()
|
||||
const distToCentre = Distance.pointToSegment(inCentre, tri.p0, tri.p1)
|
||||
return distToCentre < Math.abs(bufferDistance)
|
||||
}
|
||||
static isRingCurveInverted(inputPts, distance, curvePts) {
|
||||
if (distance === 0.0) return false
|
||||
if (inputPts.length <= 3) return false
|
||||
if (inputPts.length >= BufferCurveSetBuilder.MAX_INVERTED_RING_SIZE) return false
|
||||
if (curvePts.length > BufferCurveSetBuilder.INVERTED_CURVE_VERTEX_FACTOR * inputPts.length) return false
|
||||
const distTol = BufferCurveSetBuilder.NEARNESS_FACTOR * Math.abs(distance)
|
||||
const maxDist = BufferCurveSetBuilder.maxDistance(curvePts, inputPts)
|
||||
const isCurveTooClose = maxDist < distTol
|
||||
return isCurveTooClose
|
||||
}
|
||||
static maxDistance(pts, line) {
|
||||
let maxDistance = 0
|
||||
for (const p of pts) {
|
||||
const dist = Distance.pointToSegmentString(p, line)
|
||||
if (dist > maxDistance)
|
||||
maxDistance = dist
|
||||
|
||||
}
|
||||
return maxDistance
|
||||
}
|
||||
static isErodedCompletely(ring, bufferDistance) {
|
||||
const ringCoord = ring.getCoordinates()
|
||||
if (ringCoord.length < 4) return bufferDistance < 0
|
||||
if (ringCoord.length === 4) return BufferCurveSetBuilder.isTriangleErodedCompletely(ringCoord, bufferDistance)
|
||||
const env = ring.getEnvelopeInternal()
|
||||
const envMinDimension = Math.min(env.getHeight(), env.getWidth())
|
||||
if (bufferDistance < 0.0 && 2 * Math.abs(bufferDistance) > envMinDimension) return true
|
||||
return false
|
||||
}
|
||||
addRingSide(coord, offsetDistance, side, cwLeftLoc, cwRightLoc) {
|
||||
if (offsetDistance === 0.0 && coord.length < LinearRing.MINIMUM_VALID_SIZE) return null
|
||||
let leftLoc = cwLeftLoc
|
||||
let rightLoc = cwRightLoc
|
||||
const isCCW = this.isRingCCW(coord)
|
||||
if (coord.length >= LinearRing.MINIMUM_VALID_SIZE && isCCW) {
|
||||
leftLoc = cwRightLoc
|
||||
rightLoc = cwLeftLoc
|
||||
side = Position.opposite(side)
|
||||
}
|
||||
const curve = this._curveBuilder.getRingCurve(coord, side, offsetDistance)
|
||||
if (BufferCurveSetBuilder.isRingCurveInverted(coord, offsetDistance, curve))
|
||||
return null
|
||||
|
||||
this.addCurve(curve, leftLoc, rightLoc)
|
||||
}
|
||||
isRingCCW(coord) {
|
||||
const isCCW = Orientation.isCCWArea(coord)
|
||||
if (this._isInvertOrientation) return !isCCW
|
||||
return isCCW
|
||||
}
|
||||
addRingBothSides(coord, distance) {
|
||||
this.addRingSide(coord, distance, Position.LEFT, Location.EXTERIOR, Location.INTERIOR)
|
||||
this.addRingSide(coord, distance, Position.RIGHT, Location.INTERIOR, Location.EXTERIOR)
|
||||
}
|
||||
addPoint(p) {
|
||||
if (this._distance <= 0.0) return null
|
||||
const coord = p.getCoordinates()
|
||||
if (coord.length >= 1 && !coord[0].isValid()) return null
|
||||
const curve = this._curveBuilder.getLineCurve(coord, this._distance)
|
||||
this.addCurve(curve, Location.EXTERIOR, Location.INTERIOR)
|
||||
}
|
||||
addPolygon(p) {
|
||||
let offsetDistance = this._distance
|
||||
let offsetSide = Position.LEFT
|
||||
if (this._distance < 0.0) {
|
||||
offsetDistance = -this._distance
|
||||
offsetSide = Position.RIGHT
|
||||
}
|
||||
const shell = p.getExteriorRing()
|
||||
const shellCoord = BufferCurveSetBuilder.clean(shell.getCoordinates())
|
||||
if (this._distance < 0.0 && BufferCurveSetBuilder.isErodedCompletely(shell, this._distance)) return null
|
||||
if (this._distance <= 0.0 && shellCoord.length < 3) return null
|
||||
this.addRingSide(shellCoord, offsetDistance, offsetSide, Location.EXTERIOR, Location.INTERIOR)
|
||||
for (let i = 0; i < p.getNumInteriorRing(); i++) {
|
||||
const hole = p.getInteriorRingN(i)
|
||||
const holeCoord = BufferCurveSetBuilder.clean(hole.getCoordinates())
|
||||
if (this._distance > 0.0 && BufferCurveSetBuilder.isErodedCompletely(hole, -this._distance)) continue
|
||||
this.addRingSide(holeCoord, offsetDistance, Position.opposite(offsetSide), Location.INTERIOR, Location.EXTERIOR)
|
||||
}
|
||||
}
|
||||
setInvertOrientation(isInvertOrientation) {
|
||||
this._isInvertOrientation = isInvertOrientation
|
||||
}
|
||||
addLineString(line) {
|
||||
if (this._curveBuilder.isLineOffsetEmpty(this._distance)) return null
|
||||
const coord = BufferCurveSetBuilder.clean(line.getCoordinates())
|
||||
if (CoordinateArrays.isRing(coord) && !this._curveBuilder.getBufferParameters().isSingleSided()) {
|
||||
this.addRingBothSides(coord, this._distance)
|
||||
} else {
|
||||
const curve = this._curveBuilder.getLineCurve(coord, this._distance)
|
||||
this.addCurve(curve, Location.EXTERIOR, Location.INTERIOR)
|
||||
}
|
||||
}
|
||||
addCurve(coord, leftLoc, rightLoc) {
|
||||
if (coord === null || coord.length < 2) return null
|
||||
const e = new NodedSegmentString(coord, new Label(0, Location.BOUNDARY, leftLoc, rightLoc))
|
||||
this._curveList.add(e)
|
||||
}
|
||||
getCurves() {
|
||||
this.add(this._inputGeom)
|
||||
return this._curveList
|
||||
}
|
||||
add(g) {
|
||||
if (g.isEmpty()) return null
|
||||
if (g instanceof Polygon) this.addPolygon(g); else if (g instanceof LineString) this.addLineString(g); else if (g instanceof Point) this.addPoint(g); else if (g instanceof MultiPoint) this.addCollection(g); else if (g instanceof MultiLineString) this.addCollection(g); else if (g instanceof MultiPolygon) this.addCollection(g); else if (g instanceof GeometryCollection) this.addCollection(g); else throw new UnsupportedOperationException(g.getClass().getName())
|
||||
}
|
||||
addCollection(gc) {
|
||||
for (let i = 0; i < gc.getNumGeometries(); i++) {
|
||||
const g = gc.getGeometryN(i)
|
||||
this.add(g)
|
||||
}
|
||||
}
|
||||
}
|
||||
BufferCurveSetBuilder.MAX_INVERTED_RING_SIZE = 9
|
||||
BufferCurveSetBuilder.INVERTED_CURVE_VERTEX_FACTOR = 4
|
||||
BufferCurveSetBuilder.NEARNESS_FACTOR = 0.99
|
||||
@ -1,238 +0,0 @@
|
||||
import BufferParameters from './BufferParameters.js'
|
||||
import MonotoneChainSelectAction from '../../index/chain/MonotoneChainSelectAction.js'
|
||||
import LineString from '../../geom/LineString.js'
|
||||
import CoordinateList from '../../geom/CoordinateList.js'
|
||||
import MonotoneChain from '../../index/chain/MonotoneChain.js'
|
||||
import Point from '../../geom/Point.js'
|
||||
import Polygon from '../../geom/Polygon.js'
|
||||
import GeometryMapper from '../../geom/util/GeometryMapper.js'
|
||||
import SegmentMCIndex from './SegmentMCIndex.js'
|
||||
import OffsetCurveBuilder from './OffsetCurveBuilder.js'
|
||||
import LinearRing from '../../geom/LinearRing.js'
|
||||
import BufferOp from './BufferOp.js'
|
||||
import LineSegment from '../../geom/LineSegment.js'
|
||||
import Envelope from '../../geom/Envelope.js'
|
||||
import Distance from '../../algorithm/Distance.js'
|
||||
export default class OffsetCurve {
|
||||
constructor() {
|
||||
OffsetCurve.constructor_.apply(this, arguments)
|
||||
}
|
||||
static constructor_() {
|
||||
this._inputGeom = null
|
||||
this._distance = null
|
||||
this._bufferParams = null
|
||||
this._matchDistance = null
|
||||
this._geomFactory = null
|
||||
if (arguments.length === 2) {
|
||||
const geom = arguments[0], distance = arguments[1]
|
||||
OffsetCurve.constructor_.call(this, geom, distance, null)
|
||||
} else if (arguments.length === 3) {
|
||||
const geom = arguments[0], distance = arguments[1], bufParams = arguments[2]
|
||||
this._inputGeom = geom
|
||||
this._distance = distance
|
||||
this._matchDistance = Math.abs(distance) / OffsetCurve.NEARNESS_FACTOR
|
||||
this._geomFactory = this._inputGeom.getFactory()
|
||||
this._bufferParams = new BufferParameters()
|
||||
if (bufParams !== null) {
|
||||
this._bufferParams.setQuadrantSegments(bufParams.getQuadrantSegments())
|
||||
this._bufferParams.setJoinStyle(bufParams.getJoinStyle())
|
||||
this._bufferParams.setMitreLimit(bufParams.getMitreLimit())
|
||||
}
|
||||
}
|
||||
}
|
||||
static subsegmentMatchFrac(p0, p1, seg0, seg1, matchDistance) {
|
||||
if (matchDistance < Distance.pointToSegment(p0, seg0, seg1)) return -1
|
||||
if (matchDistance < Distance.pointToSegment(p1, seg0, seg1)) return -1
|
||||
const seg = new LineSegment(seg0, seg1)
|
||||
return seg.segmentFraction(p0)
|
||||
}
|
||||
static extractMaxAreaPolygon(geom) {
|
||||
if (geom.getNumGeometries() === 1) return geom
|
||||
let maxArea = 0
|
||||
let maxPoly = null
|
||||
for (let i = 0; i < geom.getNumGeometries(); i++) {
|
||||
const poly = geom.getGeometryN(i)
|
||||
const area = poly.getArea()
|
||||
if (maxPoly === null || area > maxArea) {
|
||||
maxPoly = poly
|
||||
maxArea = area
|
||||
}
|
||||
}
|
||||
return maxPoly
|
||||
}
|
||||
static extractSection(ring, startIndex, isExtracted) {
|
||||
if (startIndex < 0) return new Array(0).fill(null)
|
||||
const coordList = new CoordinateList()
|
||||
let i = startIndex
|
||||
do {
|
||||
coordList.add(ring[i], false)
|
||||
if (!isExtracted[i])
|
||||
break
|
||||
|
||||
i = OffsetCurve.next(i, ring.length - 1)
|
||||
} while (i !== startIndex)
|
||||
if (isExtracted[i])
|
||||
coordList.add(ring[i], false)
|
||||
|
||||
if (coordList.size() === 1) return new Array(0).fill(null)
|
||||
return coordList.toCoordinateArray()
|
||||
}
|
||||
static next(i, size) {
|
||||
i += 1
|
||||
return i < size ? i : 0
|
||||
}
|
||||
static getBufferOriented(geom, distance, bufParams) {
|
||||
const buffer = BufferOp.bufferOp(geom, Math.abs(distance), bufParams)
|
||||
let bufferPoly = OffsetCurve.extractMaxAreaPolygon(buffer)
|
||||
if (distance < 0)
|
||||
bufferPoly = bufferPoly.reverse()
|
||||
|
||||
return bufferPoly
|
||||
}
|
||||
static extractLongestHole(poly) {
|
||||
let largestHole = null
|
||||
let maxLen = -1
|
||||
for (let i = 0; i < poly.getNumInteriorRing(); i++) {
|
||||
const hole = poly.getInteriorRingN(i)
|
||||
const len = hole.getLength()
|
||||
if (len > maxLen) {
|
||||
largestHole = hole
|
||||
maxLen = len
|
||||
}
|
||||
}
|
||||
return largestHole
|
||||
}
|
||||
static rawOffset() {
|
||||
if (arguments.length === 2) {
|
||||
const geom = arguments[0], distance = arguments[1]
|
||||
return OffsetCurve.rawOffset(geom, distance, new BufferParameters())
|
||||
} else if (arguments.length === 3) {
|
||||
const geom = arguments[0], distance = arguments[1], bufParams = arguments[2]
|
||||
const ocb = new OffsetCurveBuilder(geom.getFactory().getPrecisionModel(), bufParams)
|
||||
const pts = ocb.getOffsetCurve(geom.getCoordinates(), distance)
|
||||
return pts
|
||||
}
|
||||
}
|
||||
static getCurve() {
|
||||
if (arguments.length === 2) {
|
||||
const geom = arguments[0], distance = arguments[1]
|
||||
const oc = new OffsetCurve(geom, distance)
|
||||
return oc.getCurve()
|
||||
} else if (arguments.length === 5) {
|
||||
const geom = arguments[0], distance = arguments[1], quadSegs = arguments[2], joinStyle = arguments[3], mitreLimit = arguments[4]
|
||||
const bufferParams = new BufferParameters()
|
||||
if (quadSegs >= 0) bufferParams.setQuadrantSegments(quadSegs)
|
||||
if (joinStyle >= 0) bufferParams.setJoinStyle(joinStyle)
|
||||
if (mitreLimit >= 0) bufferParams.setMitreLimit(mitreLimit)
|
||||
const oc = new OffsetCurve(geom, distance, bufferParams)
|
||||
return oc.getCurve()
|
||||
}
|
||||
}
|
||||
getCurve() {
|
||||
return GeometryMapper.flatMap(this._inputGeom, 1, new (class {
|
||||
get interfaces_() {
|
||||
return [MapOp]
|
||||
}
|
||||
map(geom) {
|
||||
if (geom instanceof Point) return null
|
||||
if (geom instanceof Polygon)
|
||||
return this.toLineString(geom.buffer(this._distance).getBoundary())
|
||||
|
||||
return this.computeCurve(geom, this._distance)
|
||||
}
|
||||
toLineString(geom) {
|
||||
if (geom instanceof LinearRing) {
|
||||
const ring = geom
|
||||
return geom.getFactory().createLineString(ring.getCoordinateSequence())
|
||||
}
|
||||
return geom
|
||||
}
|
||||
})())
|
||||
}
|
||||
computeCurve() {
|
||||
if (arguments[0] instanceof LineString && typeof arguments[1] === 'number') {
|
||||
const lineGeom = arguments[0], distance = arguments[1]
|
||||
if (lineGeom.getNumPoints() < 2 || lineGeom.getLength() === 0.0)
|
||||
return this._geomFactory.createLineString()
|
||||
|
||||
if (lineGeom.getNumPoints() === 2)
|
||||
return this.offsetSegment(lineGeom.getCoordinates(), distance)
|
||||
|
||||
const rawOffset = OffsetCurve.rawOffset(lineGeom, distance, this._bufferParams)
|
||||
if (rawOffset.length === 0)
|
||||
return this._geomFactory.createLineString()
|
||||
|
||||
const bufferPoly = OffsetCurve.getBufferOriented(lineGeom, distance, this._bufferParams)
|
||||
const shell = bufferPoly.getExteriorRing().getCoordinates()
|
||||
let offsetCurve = this.computeCurve(shell, rawOffset)
|
||||
if (!offsetCurve.isEmpty() || bufferPoly.getNumInteriorRing() === 0) return offsetCurve
|
||||
const holePts = OffsetCurve.extractLongestHole(bufferPoly).getCoordinates()
|
||||
offsetCurve = this.computeCurve(holePts, rawOffset)
|
||||
return offsetCurve
|
||||
} else if (arguments[0] instanceof Array && arguments[1] instanceof Array) {
|
||||
const bufferPts = arguments[0], rawOffset = arguments[1]
|
||||
const isInCurve = new Array(bufferPts.length - 1).fill(null)
|
||||
const segIndex = new SegmentMCIndex(bufferPts)
|
||||
let curveStart = -1
|
||||
for (let i = 0; i < rawOffset.length - 1; i++) {
|
||||
const index = this.markMatchingSegments(rawOffset[i], rawOffset[i + 1], segIndex, bufferPts, isInCurve)
|
||||
if (curveStart < 0)
|
||||
curveStart = index
|
||||
|
||||
}
|
||||
const curvePts = OffsetCurve.extractSection(bufferPts, curveStart, isInCurve)
|
||||
return this._geomFactory.createLineString(curvePts)
|
||||
}
|
||||
}
|
||||
offsetSegment(pts, distance) {
|
||||
const offsetSeg = new LineSegment(pts[0], pts[1]).offset(distance)
|
||||
return this._geomFactory.createLineString([offsetSeg.p0, offsetSeg.p1])
|
||||
}
|
||||
markMatchingSegments(p0, p1, segIndex, bufferPts, isInCurve) {
|
||||
const matchEnv = new Envelope(p0, p1)
|
||||
matchEnv.expandBy(this._matchDistance)
|
||||
const action = new MatchCurveSegmentAction(p0, p1, bufferPts, this._matchDistance, isInCurve)
|
||||
segIndex.query(matchEnv, action)
|
||||
return action.getMinCurveIndex()
|
||||
}
|
||||
}
|
||||
class MatchCurveSegmentAction extends MonotoneChainSelectAction {
|
||||
constructor() {
|
||||
super()
|
||||
MatchCurveSegmentAction.constructor_.apply(this, arguments)
|
||||
}
|
||||
static constructor_() {
|
||||
this._p0 = null
|
||||
this._p1 = null
|
||||
this._bufferPts = null
|
||||
this._matchDistance = null
|
||||
this._isInCurve = null
|
||||
this._minFrac = -1
|
||||
this._minCurveIndex = -1
|
||||
const p0 = arguments[0], p1 = arguments[1], bufferPts = arguments[2], matchDistance = arguments[3], isInCurve = arguments[4]
|
||||
this._p0 = p0
|
||||
this._p1 = p1
|
||||
this._bufferPts = bufferPts
|
||||
this._matchDistance = matchDistance
|
||||
this._isInCurve = isInCurve
|
||||
}
|
||||
select() {
|
||||
if (arguments.length === 2 && (Number.isInteger(arguments[1]) && arguments[0] instanceof MonotoneChain)) {
|
||||
const mc = arguments[0], segIndex = arguments[1]
|
||||
const frac = OffsetCurve.subsegmentMatchFrac(this._bufferPts[segIndex], this._bufferPts[segIndex + 1], this._p0, this._p1, this._matchDistance)
|
||||
if (frac < 0) return null
|
||||
this._isInCurve[segIndex] = true
|
||||
if (this._minFrac < 0 || frac < this._minFrac) {
|
||||
this._minFrac = frac
|
||||
this._minCurveIndex = segIndex
|
||||
}
|
||||
} else {
|
||||
return super.select.apply(this, arguments)
|
||||
}
|
||||
}
|
||||
getMinCurveIndex() {
|
||||
return this._minCurveIndex
|
||||
}
|
||||
}
|
||||
OffsetCurve.MatchCurveSegmentAction = MatchCurveSegmentAction
|
||||
OffsetCurve.NEARNESS_FACTOR = 10000
|
||||
@ -112,12 +112,12 @@ export default class OffsetCurveBuilder {
|
||||
if (inputPts.length <= 1) {
|
||||
this.computePointCurve(inputPts[0], segGen)
|
||||
} else
|
||||
if (this._bufParams.isSingleSided()) {
|
||||
const isRightSide = distance < 0.0
|
||||
this.computeSingleSidedBufferCurve(inputPts, isRightSide, segGen)
|
||||
} else {
|
||||
this.computeLineBufferCurve(inputPts, segGen)
|
||||
}
|
||||
if (this._bufParams.isSingleSided()) {
|
||||
const isRightSide = distance < 0.0
|
||||
this.computeSingleSidedBufferCurve(inputPts, isRightSide, segGen)
|
||||
} else {
|
||||
this.computeLineBufferCurve(inputPts, segGen)
|
||||
}
|
||||
|
||||
const lineCoord = segGen.getCoordinates()
|
||||
return lineCoord
|
||||
|
||||
@ -1,32 +0,0 @@
|
||||
import STRtree from '../../index/strtree/STRtree.js'
|
||||
import ItemVisitor from '../../index/ItemVisitor.js'
|
||||
import MonotoneChainBuilder from '../../index/chain/MonotoneChainBuilder.js'
|
||||
export default class SegmentMCIndex {
|
||||
constructor() {
|
||||
SegmentMCIndex.constructor_.apply(this, arguments)
|
||||
}
|
||||
static constructor_() {
|
||||
this._index = null
|
||||
const segs = arguments[0]
|
||||
this._index = this.buildIndex(segs)
|
||||
}
|
||||
buildIndex(segs) {
|
||||
const index = new STRtree()
|
||||
const segChains = MonotoneChainBuilder.getChains(segs, segs)
|
||||
for (const mc of segChains)
|
||||
index.insert(mc.getEnvelope(), mc)
|
||||
|
||||
return index
|
||||
}
|
||||
query(env, action) {
|
||||
this._index.query(env, new (class {
|
||||
get interfaces_() {
|
||||
return [ItemVisitor]
|
||||
}
|
||||
visitItem(item) {
|
||||
const testChain = item
|
||||
testChain.select(env, action)
|
||||
}
|
||||
})())
|
||||
}
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
import OverlayNG from './OverlayNG.js'
|
||||
import SegmentExtractingNoder from '../../noding/SegmentExtractingNoder.js'
|
||||
import BoundaryChainNoder from '../../noding/BoundaryChainNoder.js'
|
||||
export default class CoverageUnion {
|
||||
static union(coverage) {
|
||||
let noder = new BoundaryChainNoder()
|
||||
if (coverage.getDimension() < 2)
|
||||
noder = new SegmentExtractingNoder()
|
||||
|
||||
return OverlayNG.union(coverage, null, noder)
|
||||
}
|
||||
}
|
||||
@ -1,184 +0,0 @@
|
||||
import Location from '../../geom/Location.js'
|
||||
import OverlayLabel from './OverlayLabel.js'
|
||||
import WKTWriter from '../../io/WKTWriter.js'
|
||||
import Integer from '../../../../../java/lang/Integer.js'
|
||||
import Dimension from '../../geom/Dimension.js'
|
||||
import IllegalStateException from '../../../../../java/lang/IllegalStateException.js'
|
||||
export default class Edge {
|
||||
constructor() {
|
||||
Edge.constructor_.apply(this, arguments)
|
||||
}
|
||||
static constructor_() {
|
||||
this._pts = null
|
||||
this._aDim = OverlayLabel.DIM_UNKNOWN
|
||||
this._aDepthDelta = 0
|
||||
this._aIsHole = false
|
||||
this._bDim = OverlayLabel.DIM_UNKNOWN
|
||||
this._bDepthDelta = 0
|
||||
this._bIsHole = false
|
||||
const pts = arguments[0], info = arguments[1]
|
||||
this._pts = pts
|
||||
this.copyInfo(info)
|
||||
}
|
||||
static isCollapsed(pts) {
|
||||
if (pts.length < 2) return true
|
||||
if (pts[0].equals2D(pts[1])) return true
|
||||
if (pts.length > 2)
|
||||
if (pts[pts.length - 1].equals2D(pts[pts.length - 2])) return true
|
||||
|
||||
return false
|
||||
}
|
||||
static hasAreaParent(dim) {
|
||||
return dim === OverlayLabel.DIM_BOUNDARY || dim === OverlayLabel.DIM_COLLAPSE
|
||||
}
|
||||
static labelDim(dim, depthDelta) {
|
||||
if (dim === Dimension.FALSE) return OverlayLabel.DIM_NOT_PART
|
||||
if (dim === Dimension.L) return OverlayLabel.DIM_LINE
|
||||
const isCollapse = depthDelta === 0
|
||||
if (isCollapse) return OverlayLabel.DIM_COLLAPSE
|
||||
return OverlayLabel.DIM_BOUNDARY
|
||||
}
|
||||
static infoString(index, dim, isHole, depthDelta) {
|
||||
return (index === 0 ? 'A:' : 'B:') + OverlayLabel.dimensionSymbol(dim) + Edge.ringRoleSymbol(dim, isHole) + Integer.toString(depthDelta)
|
||||
}
|
||||
static initLabel(lbl, geomIndex, dim, depthDelta, isHole) {
|
||||
const dimLabel = Edge.labelDim(dim, depthDelta)
|
||||
switch (dimLabel) {
|
||||
case OverlayLabel.DIM_NOT_PART:
|
||||
lbl.initNotPart(geomIndex)
|
||||
break
|
||||
case OverlayLabel.DIM_BOUNDARY:
|
||||
lbl.initBoundary(geomIndex, Edge.locationLeft(depthDelta), Edge.locationRight(depthDelta), isHole)
|
||||
break
|
||||
case OverlayLabel.DIM_COLLAPSE:
|
||||
lbl.initCollapse(geomIndex, isHole)
|
||||
break
|
||||
case OverlayLabel.DIM_LINE:
|
||||
lbl.initLine(geomIndex)
|
||||
break
|
||||
}
|
||||
}
|
||||
static locationRight(depthDelta) {
|
||||
const delSign = Edge.delSign(depthDelta)
|
||||
switch (delSign) {
|
||||
case 0:
|
||||
return OverlayLabel.LOC_UNKNOWN
|
||||
case 1:
|
||||
return Location.INTERIOR
|
||||
case -1:
|
||||
return Location.EXTERIOR
|
||||
}
|
||||
return OverlayLabel.LOC_UNKNOWN
|
||||
}
|
||||
static toStringPts(pts) {
|
||||
const orig = pts[0]
|
||||
const dest = pts[pts.length - 1]
|
||||
const dirPtStr = pts.length > 2 ? ', ' + WKTWriter.format(pts[1]) : ''
|
||||
const ptsStr = WKTWriter.format(orig) + dirPtStr + ' .. ' + WKTWriter.format(dest)
|
||||
return ptsStr
|
||||
}
|
||||
static delSign(depthDel) {
|
||||
if (depthDel > 0) return 1
|
||||
if (depthDel < 0) return -1
|
||||
return 0
|
||||
}
|
||||
static locationLeft(depthDelta) {
|
||||
const delSign = Edge.delSign(depthDelta)
|
||||
switch (delSign) {
|
||||
case 0:
|
||||
return OverlayLabel.LOC_UNKNOWN
|
||||
case 1:
|
||||
return Location.EXTERIOR
|
||||
case -1:
|
||||
return Location.INTERIOR
|
||||
}
|
||||
return OverlayLabel.LOC_UNKNOWN
|
||||
}
|
||||
static ringRoleSymbol(dim, isHole) {
|
||||
if (Edge.hasAreaParent(dim)) return '' + OverlayLabel.ringRoleSymbol(isHole)
|
||||
return ''
|
||||
}
|
||||
static isHoleMerged(geomIndex, edge1, edge2) {
|
||||
const isShell1 = edge1.isShell(geomIndex)
|
||||
const isShell2 = edge2.isShell(geomIndex)
|
||||
const isShellMerged = isShell1 || isShell2
|
||||
return !isShellMerged
|
||||
}
|
||||
getCoordinates() {
|
||||
return this._pts
|
||||
}
|
||||
size() {
|
||||
return this._pts.length
|
||||
}
|
||||
getCoordinate(index) {
|
||||
return this._pts[index]
|
||||
}
|
||||
direction() {
|
||||
const pts = this.getCoordinates()
|
||||
if (pts.length < 2)
|
||||
throw new IllegalStateException('Edge must have >= 2 points')
|
||||
|
||||
const p0 = pts[0]
|
||||
const p1 = pts[1]
|
||||
const pn0 = pts[pts.length - 1]
|
||||
const pn1 = pts[pts.length - 2]
|
||||
let cmp = 0
|
||||
const cmp0 = p0.compareTo(pn0)
|
||||
if (cmp0 !== 0) cmp = cmp0
|
||||
if (cmp === 0) {
|
||||
const cmp1 = p1.compareTo(pn1)
|
||||
if (cmp1 !== 0) cmp = cmp1
|
||||
}
|
||||
if (cmp === 0)
|
||||
throw new IllegalStateException('Edge direction cannot be determined because endpoints are equal')
|
||||
|
||||
return cmp === -1
|
||||
}
|
||||
createLabel() {
|
||||
const lbl = new OverlayLabel()
|
||||
Edge.initLabel(lbl, 0, this._aDim, this._aDepthDelta, this._aIsHole)
|
||||
Edge.initLabel(lbl, 1, this._bDim, this._bDepthDelta, this._bIsHole)
|
||||
return lbl
|
||||
}
|
||||
relativeDirection(edge2) {
|
||||
if (!this.getCoordinate(0).equals2D(edge2.getCoordinate(0))) return false
|
||||
if (!this.getCoordinate(1).equals2D(edge2.getCoordinate(1))) return false
|
||||
return true
|
||||
}
|
||||
copyInfo(info) {
|
||||
if (info.getIndex() === 0) {
|
||||
this._aDim = info.getDimension()
|
||||
this._aIsHole = info.isHole()
|
||||
this._aDepthDelta = info.getDepthDelta()
|
||||
} else {
|
||||
this._bDim = info.getDimension()
|
||||
this._bIsHole = info.isHole()
|
||||
this._bDepthDelta = info.getDepthDelta()
|
||||
}
|
||||
}
|
||||
isShell(geomIndex) {
|
||||
if (geomIndex === 0)
|
||||
return this._aDim === OverlayLabel.DIM_BOUNDARY && !this._aIsHole
|
||||
|
||||
return this._bDim === OverlayLabel.DIM_BOUNDARY && !this._bIsHole
|
||||
}
|
||||
merge(edge) {
|
||||
this._aIsHole = Edge.isHoleMerged(0, this, edge)
|
||||
this._bIsHole = Edge.isHoleMerged(1, this, edge)
|
||||
if (edge._aDim > this._aDim) this._aDim = edge._aDim
|
||||
if (edge._bDim > this._bDim) this._bDim = edge._bDim
|
||||
const relDir = this.relativeDirection(edge)
|
||||
const flipFactor = relDir ? 1 : -1
|
||||
this._aDepthDelta += flipFactor * edge._aDepthDelta
|
||||
this._bDepthDelta += flipFactor * edge._bDepthDelta
|
||||
}
|
||||
toLineString() {
|
||||
return WKTWriter.toLineString(this._pts)
|
||||
}
|
||||
toString() {
|
||||
const ptsStr = Edge.toStringPts(this._pts)
|
||||
const aInfo = Edge.infoString(0, this._aDim, this._aIsHole, this._aDepthDelta)
|
||||
const bInfo = Edge.infoString(1, this._bDim, this._bIsHole, this._bDepthDelta)
|
||||
return 'Edge( ' + ptsStr + ' ) ' + aInfo + '/' + bInfo
|
||||
}
|
||||
}
|
||||
@ -1,76 +0,0 @@
|
||||
import OrdinateFormat from '../../io/OrdinateFormat.js'
|
||||
import Double from '../../../../../java/lang/Double.js'
|
||||
import Comparable from '../../../../../java/lang/Comparable.js'
|
||||
export default class EdgeKey {
|
||||
constructor() {
|
||||
EdgeKey.constructor_.apply(this, arguments)
|
||||
}
|
||||
static constructor_() {
|
||||
this._p0x = null
|
||||
this._p0y = null
|
||||
this._p1x = null
|
||||
this._p1y = null
|
||||
const edge = arguments[0]
|
||||
this.initPoints(edge)
|
||||
}
|
||||
static create(edge) {
|
||||
return new EdgeKey(edge)
|
||||
}
|
||||
static hashCode() {
|
||||
if (arguments.length === 1 && typeof arguments[0] === 'number') {
|
||||
const x = arguments[0]
|
||||
const f = Double.doubleToLongBits(x)
|
||||
return Math.trunc(f ^ f >>> 32)
|
||||
}
|
||||
}
|
||||
format(x, y) {
|
||||
return OrdinateFormat.DEFAULT.format(x) + ' ' + OrdinateFormat.DEFAULT.format(y)
|
||||
}
|
||||
equals(o) {
|
||||
if (!(o instanceof EdgeKey))
|
||||
return false
|
||||
|
||||
const ek = o
|
||||
return this._p0x === ek._p0x && this._p0y === ek._p0y && this._p1x === ek._p1x && this._p1y === ek._p1y
|
||||
}
|
||||
initPoints(edge) {
|
||||
const direction = edge.direction()
|
||||
if (direction) {
|
||||
this.init(edge.getCoordinate(0), edge.getCoordinate(1))
|
||||
} else {
|
||||
const len = edge.size()
|
||||
this.init(edge.getCoordinate(len - 1), edge.getCoordinate(len - 2))
|
||||
}
|
||||
}
|
||||
compareTo(ek) {
|
||||
if (this._p0x < ek._p0x) return -1
|
||||
if (this._p0x > ek._p0x) return 1
|
||||
if (this._p0y < ek._p0y) return -1
|
||||
if (this._p0y > ek._p0y) return 1
|
||||
if (this._p1x < ek._p1x) return -1
|
||||
if (this._p1x > ek._p1x) return 1
|
||||
if (this._p1y < ek._p1y) return -1
|
||||
if (this._p1y > ek._p1y) return 1
|
||||
return 0
|
||||
}
|
||||
toString() {
|
||||
return 'EdgeKey(' + this.format(this._p0x, this._p0y) + ', ' + this.format(this._p1x, this._p1y) + ')'
|
||||
}
|
||||
init(p0, p1) {
|
||||
this._p0x = p0.getX()
|
||||
this._p0y = p0.getY()
|
||||
this._p1x = p1.getX()
|
||||
this._p1y = p1.getY()
|
||||
}
|
||||
hashCode() {
|
||||
let result = 17
|
||||
result = 37 * result + EdgeKey.hashCode(this._p0x)
|
||||
result = 37 * result + EdgeKey.hashCode(this._p0y)
|
||||
result = 37 * result + EdgeKey.hashCode(this._p1x)
|
||||
result = 37 * result + EdgeKey.hashCode(this._p1y)
|
||||
return result
|
||||
}
|
||||
get interfaces_() {
|
||||
return [Comparable]
|
||||
}
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
import HashMap from '../../../../../java/util/HashMap.js'
|
||||
import ArrayList from '../../../../../java/util/ArrayList.js'
|
||||
import Assert from '../../util/Assert.js'
|
||||
import EdgeKey from './EdgeKey.js'
|
||||
export default class EdgeMerger {
|
||||
static merge(edges) {
|
||||
const mergedEdges = new ArrayList()
|
||||
const edgeMap = new HashMap()
|
||||
for (const edge of edges) {
|
||||
const edgeKey = EdgeKey.create(edge)
|
||||
const baseEdge = edgeMap.get(edgeKey)
|
||||
if (baseEdge === null) {
|
||||
edgeMap.put(edgeKey, edge)
|
||||
mergedEdges.add(edge)
|
||||
} else {
|
||||
Assert.isTrue(baseEdge.size() === edge.size(), 'Merge of edges of different sizes - probable noding error.')
|
||||
baseEdge.merge(edge)
|
||||
}
|
||||
}
|
||||
return mergedEdges
|
||||
}
|
||||
}
|
||||
@ -1,198 +0,0 @@
|
||||
import LineString from '../../geom/LineString.js'
|
||||
import ValidatingNoder from '../../noding/ValidatingNoder.js'
|
||||
import IllegalArgumentException from '../../../../../java/lang/IllegalArgumentException.js'
|
||||
import MCIndexNoder from '../../noding/MCIndexNoder.js'
|
||||
import NodedSegmentString from '../../noding/NodedSegmentString.js'
|
||||
import Polygon from '../../geom/Polygon.js'
|
||||
import RingClipper from './RingClipper.js'
|
||||
import SnapRoundingNoder from '../../noding/snapround/SnapRoundingNoder.js'
|
||||
import EdgeMerger from './EdgeMerger.js'
|
||||
import Orientation from '../../algorithm/Orientation.js'
|
||||
import MultiPolygon from '../../geom/MultiPolygon.js'
|
||||
import OverlayUtil from './OverlayUtil.js'
|
||||
import GeometryCollection from '../../geom/GeometryCollection.js'
|
||||
import LineLimiter from './LineLimiter.js'
|
||||
import CoordinateArrays from '../../geom/CoordinateArrays.js'
|
||||
import ArrayList from '../../../../../java/util/ArrayList.js'
|
||||
import RobustLineIntersector from '../../algorithm/RobustLineIntersector.js'
|
||||
import IntersectionAdder from '../../noding/IntersectionAdder.js'
|
||||
import Edge from './Edge.js'
|
||||
import MultiLineString from '../../geom/MultiLineString.js'
|
||||
import EdgeSourceInfo from './EdgeSourceInfo.js'
|
||||
export default class EdgeNodingBuilder {
|
||||
constructor() {
|
||||
EdgeNodingBuilder.constructor_.apply(this, arguments)
|
||||
}
|
||||
static constructor_() {
|
||||
this._pm = null
|
||||
this.inputEdges = new ArrayList()
|
||||
this._customNoder = null
|
||||
this._clipEnv = null
|
||||
this._clipper = null
|
||||
this._limiter = null
|
||||
this._hasEdges = new Array(2).fill(null)
|
||||
const pm = arguments[0], noder = arguments[1]
|
||||
this._pm = pm
|
||||
this._customNoder = noder
|
||||
}
|
||||
static createFixedPrecisionNoder(pm) {
|
||||
const noder = new SnapRoundingNoder(pm)
|
||||
return noder
|
||||
}
|
||||
static createFloatingPrecisionNoder(doValidation) {
|
||||
const mcNoder = new MCIndexNoder()
|
||||
const li = new RobustLineIntersector()
|
||||
mcNoder.setSegmentIntersector(new IntersectionAdder(li))
|
||||
let noder = mcNoder
|
||||
if (doValidation)
|
||||
noder = new ValidatingNoder(mcNoder)
|
||||
|
||||
return noder
|
||||
}
|
||||
static removeRepeatedPoints(line) {
|
||||
const pts = line.getCoordinates()
|
||||
return CoordinateArrays.removeRepeatedPoints(pts)
|
||||
}
|
||||
static computeDepthDelta(ring, isHole) {
|
||||
const isCCW = Orientation.isCCW(ring.getCoordinateSequence())
|
||||
let isOriented = true
|
||||
if (!isHole) isOriented = !isCCW; else
|
||||
isOriented = isCCW
|
||||
|
||||
const depthDelta = isOriented ? 1 : -1
|
||||
return depthDelta
|
||||
}
|
||||
addLine() {
|
||||
if (arguments[0] instanceof LineString && Number.isInteger(arguments[1])) {
|
||||
const line = arguments[0], geomIndex = arguments[1]
|
||||
if (line.isEmpty()) return null
|
||||
if (this.isClippedCompletely(line.getEnvelopeInternal())) return null
|
||||
if (this.isToBeLimited(line)) {
|
||||
const sections = this.limit(line)
|
||||
for (const pts of sections)
|
||||
this.addLine(pts, geomIndex)
|
||||
|
||||
} else {
|
||||
const ptsNoRepeat = EdgeNodingBuilder.removeRepeatedPoints(line)
|
||||
this.addLine(ptsNoRepeat, geomIndex)
|
||||
}
|
||||
} else if (arguments[0] instanceof Array && Number.isInteger(arguments[1])) {
|
||||
const pts = arguments[0], geomIndex = arguments[1]
|
||||
if (pts.length < 2)
|
||||
return null
|
||||
|
||||
const info = new EdgeSourceInfo(geomIndex)
|
||||
this.addEdge(pts, info)
|
||||
}
|
||||
}
|
||||
getNoder() {
|
||||
if (this._customNoder !== null) return this._customNoder
|
||||
if (OverlayUtil.isFloating(this._pm)) return EdgeNodingBuilder.createFloatingPrecisionNoder(EdgeNodingBuilder.IS_NODING_VALIDATED)
|
||||
return EdgeNodingBuilder.createFixedPrecisionNoder(this._pm)
|
||||
}
|
||||
hasEdgesFor(geomIndex) {
|
||||
return this._hasEdges[geomIndex]
|
||||
}
|
||||
addPolygon(poly, geomIndex) {
|
||||
const shell = poly.getExteriorRing()
|
||||
this.addPolygonRing(shell, false, geomIndex)
|
||||
for (let i = 0; i < poly.getNumInteriorRing(); i++) {
|
||||
const hole = poly.getInteriorRingN(i)
|
||||
this.addPolygonRing(hole, true, geomIndex)
|
||||
}
|
||||
}
|
||||
build(geom0, geom1) {
|
||||
this.add(geom0, 0)
|
||||
this.add(geom1, 1)
|
||||
const nodedEdges = this.node(this.inputEdges)
|
||||
const mergedEdges = EdgeMerger.merge(nodedEdges)
|
||||
return mergedEdges
|
||||
}
|
||||
isToBeLimited(line) {
|
||||
const pts = line.getCoordinates()
|
||||
if (this._limiter === null || pts.length <= EdgeNodingBuilder.MIN_LIMIT_PTS)
|
||||
return false
|
||||
|
||||
const env = line.getEnvelopeInternal()
|
||||
if (this._clipEnv.covers(env))
|
||||
return false
|
||||
|
||||
return true
|
||||
}
|
||||
addEdge(pts, info) {
|
||||
const ss = new NodedSegmentString(pts, info)
|
||||
this.inputEdges.add(ss)
|
||||
}
|
||||
createEdges(segStrings) {
|
||||
const edges = new ArrayList()
|
||||
for (const ss of segStrings) {
|
||||
const pts = ss.getCoordinates()
|
||||
if (Edge.isCollapsed(pts)) continue
|
||||
const info = ss.getData()
|
||||
this._hasEdges[info.getIndex()] = true
|
||||
edges.add(new Edge(ss.getCoordinates(), info))
|
||||
}
|
||||
return edges
|
||||
}
|
||||
setClipEnvelope(clipEnv) {
|
||||
this._clipEnv = clipEnv
|
||||
this._clipper = new RingClipper(clipEnv)
|
||||
this._limiter = new LineLimiter(clipEnv)
|
||||
}
|
||||
node(segStrings) {
|
||||
const noder = this.getNoder()
|
||||
noder.computeNodes(segStrings)
|
||||
const nodedSS = noder.getNodedSubstrings()
|
||||
const edges = this.createEdges(nodedSS)
|
||||
return edges
|
||||
}
|
||||
addPolygonRing(ring, isHole, index) {
|
||||
if (ring.isEmpty()) return null
|
||||
if (this.isClippedCompletely(ring.getEnvelopeInternal())) return null
|
||||
const pts = this.clip(ring)
|
||||
if (pts.length < 2)
|
||||
return null
|
||||
|
||||
const depthDelta = EdgeNodingBuilder.computeDepthDelta(ring, isHole)
|
||||
const info = new EdgeSourceInfo(index, depthDelta, isHole)
|
||||
this.addEdge(pts, info)
|
||||
}
|
||||
clip(ring) {
|
||||
const pts = ring.getCoordinates()
|
||||
const env = ring.getEnvelopeInternal()
|
||||
if (this._clipper === null || this._clipEnv.covers(env))
|
||||
return EdgeNodingBuilder.removeRepeatedPoints(ring)
|
||||
|
||||
return this._clipper.clip(pts)
|
||||
}
|
||||
limit(line) {
|
||||
const pts = line.getCoordinates()
|
||||
return this._limiter.limit(pts)
|
||||
}
|
||||
add(g, geomIndex) {
|
||||
if (g === null || g.isEmpty()) return null
|
||||
if (this.isClippedCompletely(g.getEnvelopeInternal())) return null
|
||||
if (g instanceof Polygon) this.addPolygon(g, geomIndex); else if (g instanceof LineString) this.addLine(g, geomIndex); else if (g instanceof MultiLineString) this.addCollection(g, geomIndex); else if (g instanceof MultiPolygon) this.addCollection(g, geomIndex); else if (g instanceof GeometryCollection) this.addGeometryCollection(g, geomIndex, g.getDimension())
|
||||
}
|
||||
addCollection(gc, geomIndex) {
|
||||
for (let i = 0; i < gc.getNumGeometries(); i++) {
|
||||
const g = gc.getGeometryN(i)
|
||||
this.add(g, geomIndex)
|
||||
}
|
||||
}
|
||||
isClippedCompletely(env) {
|
||||
if (this._clipEnv === null) return false
|
||||
return this._clipEnv.disjoint(env)
|
||||
}
|
||||
addGeometryCollection(gc, geomIndex, expectedDim) {
|
||||
for (let i = 0; i < gc.getNumGeometries(); i++) {
|
||||
const g = gc.getGeometryN(i)
|
||||
if (g.getDimension() !== expectedDim)
|
||||
throw new IllegalArgumentException('Overlay input is mixed-dimension')
|
||||
|
||||
this.add(g, geomIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
EdgeNodingBuilder.MIN_LIMIT_PTS = 20
|
||||
EdgeNodingBuilder.IS_NODING_VALIDATED = true
|
||||
@ -1,39 +0,0 @@
|
||||
import Dimension from '../../geom/Dimension.js'
|
||||
import Edge from './Edge.js'
|
||||
export default class EdgeSourceInfo {
|
||||
constructor() {
|
||||
EdgeSourceInfo.constructor_.apply(this, arguments)
|
||||
}
|
||||
static constructor_() {
|
||||
this._index = null
|
||||
this._dim = -999
|
||||
this._isHole = false
|
||||
this._depthDelta = 0
|
||||
if (arguments.length === 1) {
|
||||
const index = arguments[0]
|
||||
this._index = index
|
||||
this._dim = Dimension.L
|
||||
} else if (arguments.length === 3) {
|
||||
const index = arguments[0], depthDelta = arguments[1], isHole = arguments[2]
|
||||
this._index = index
|
||||
this._dim = Dimension.A
|
||||
this._depthDelta = depthDelta
|
||||
this._isHole = isHole
|
||||
}
|
||||
}
|
||||
getDimension() {
|
||||
return this._dim
|
||||
}
|
||||
isHole() {
|
||||
return this._isHole
|
||||
}
|
||||
getDepthDelta() {
|
||||
return this._depthDelta
|
||||
}
|
||||
toString() {
|
||||
return Edge.infoString(this._index, this._dim, this._isHole, this._depthDelta)
|
||||
}
|
||||
getIndex() {
|
||||
return this._index
|
||||
}
|
||||
}
|
||||
@ -1,165 +0,0 @@
|
||||
import Coordinate from '../../geom/Coordinate.js'
|
||||
import Double from '../../../../../java/lang/Double.js'
|
||||
import MathUtil from '../../math/MathUtil.js'
|
||||
import CoordinateSequenceFilter from '../../geom/CoordinateSequenceFilter.js'
|
||||
export default class ElevationModel {
|
||||
constructor() {
|
||||
ElevationModel.constructor_.apply(this, arguments)
|
||||
}
|
||||
static constructor_() {
|
||||
this._extent = null
|
||||
this._numCellX = null
|
||||
this._numCellY = null
|
||||
this._cellSizeX = null
|
||||
this._cellSizeY = null
|
||||
this._cells = null
|
||||
this._isInitialized = false
|
||||
this._hasZValue = false
|
||||
this._averageZ = Double.NaN
|
||||
const extent = arguments[0], numCellX = arguments[1], numCellY = arguments[2]
|
||||
this._extent = extent
|
||||
this._numCellX = numCellX
|
||||
this._numCellY = numCellY
|
||||
this._cellSizeX = extent.getWidth() / numCellX
|
||||
this._cellSizeY = extent.getHeight() / numCellY
|
||||
if (this._cellSizeX <= 0.0)
|
||||
this._numCellX = 1
|
||||
|
||||
if (this._cellSizeY <= 0.0)
|
||||
this._numCellY = 1
|
||||
|
||||
this._cells = Array(numCellX).fill().map(() => Array(numCellY))
|
||||
}
|
||||
static create(geom1, geom2) {
|
||||
const extent = geom1.getEnvelopeInternal().copy()
|
||||
if (geom2 !== null)
|
||||
extent.expandToInclude(geom2.getEnvelopeInternal())
|
||||
|
||||
const model = new ElevationModel(extent, ElevationModel.DEFAULT_CELL_NUM, ElevationModel.DEFAULT_CELL_NUM)
|
||||
if (geom1 !== null) model.add(geom1)
|
||||
if (geom2 !== null) model.add(geom2)
|
||||
return model
|
||||
}
|
||||
getZ(x, y) {
|
||||
if (!this._isInitialized) this.init()
|
||||
const cell = this.getCell(x, y, false)
|
||||
if (cell === null) return this._averageZ
|
||||
return cell.getZ()
|
||||
}
|
||||
populateZ(geom) {
|
||||
if (!this._hasZValue) return null
|
||||
if (!this._isInitialized) this.init()
|
||||
geom.apply(new (class {
|
||||
get interfaces_() {
|
||||
return [CoordinateSequenceFilter]
|
||||
}
|
||||
filter(seq, i) {
|
||||
if (!seq.hasZ()) {
|
||||
this._isDone = true
|
||||
return null
|
||||
}
|
||||
if (Double.isNaN(seq.getZ(i))) {
|
||||
const z = this.getZ(seq.getOrdinate(i, Coordinate.X), seq.getOrdinate(i, Coordinate.Y))
|
||||
seq.setOrdinate(i, Coordinate.Z, z)
|
||||
}
|
||||
}
|
||||
isDone() {
|
||||
return this._isDone
|
||||
}
|
||||
isGeometryChanged() {
|
||||
return false
|
||||
}
|
||||
})())
|
||||
}
|
||||
getCell(x, y, isCreateIfMissing) {
|
||||
let ix = 0
|
||||
if (this._numCellX > 1) {
|
||||
ix = Math.trunc((x - this._extent.getMinX()) / this._cellSizeX)
|
||||
ix = MathUtil.clamp(ix, 0, this._numCellX - 1)
|
||||
}
|
||||
let iy = 0
|
||||
if (this._numCellY > 1) {
|
||||
iy = Math.trunc((y - this._extent.getMinY()) / this._cellSizeY)
|
||||
iy = MathUtil.clamp(iy, 0, this._numCellY - 1)
|
||||
}
|
||||
let cell = this._cells[ix][iy]
|
||||
if (isCreateIfMissing && cell === null) {
|
||||
cell = new ElevationCell()
|
||||
this._cells[ix][iy] = cell
|
||||
}
|
||||
return cell
|
||||
}
|
||||
add() {
|
||||
if (arguments.length === 1) {
|
||||
const geom = arguments[0]
|
||||
geom.apply(new (class {
|
||||
get interfaces_() {
|
||||
return [CoordinateSequenceFilter]
|
||||
}
|
||||
filter(seq, i) {
|
||||
if (!seq.hasZ()) {
|
||||
this._hasZ = false
|
||||
|
||||
return null
|
||||
}
|
||||
const z = seq.getOrdinate(i, Coordinate.Z)
|
||||
this.add(seq.getOrdinate(i, Coordinate.X), seq.getOrdinate(i, Coordinate.Y), z)
|
||||
}
|
||||
isDone() {
|
||||
return !this._hasZ
|
||||
}
|
||||
isGeometryChanged() {
|
||||
return false
|
||||
}
|
||||
})())
|
||||
} else if (arguments.length === 3) {
|
||||
const x = arguments[0], y = arguments[1], z = arguments[2]
|
||||
if (Double.isNaN(z)) return null
|
||||
this._hasZValue = true
|
||||
const cell = this.getCell(x, y, true)
|
||||
cell.add(z)
|
||||
}
|
||||
}
|
||||
init() {
|
||||
this._isInitialized = true
|
||||
let numCells = 0
|
||||
let sumZ = 0.0
|
||||
for (let i = 0; i < this._cells.length; i++)
|
||||
for (let j = 0; j < this._cells[0].length; j++) {
|
||||
const cell = this._cells[i][j]
|
||||
if (cell !== null) {
|
||||
cell.compute()
|
||||
numCells++
|
||||
sumZ += cell.getZ()
|
||||
}
|
||||
}
|
||||
|
||||
this._averageZ = Double.NaN
|
||||
if (numCells > 0)
|
||||
this._averageZ = sumZ / numCells
|
||||
|
||||
}
|
||||
}
|
||||
class ElevationCell {
|
||||
constructor() {
|
||||
ElevationCell.constructor_.apply(this, arguments)
|
||||
}
|
||||
static constructor_() {
|
||||
this._numZ = 0
|
||||
this._sumZ = 0.0
|
||||
this._avgZ = null
|
||||
}
|
||||
add(z) {
|
||||
this._numZ++
|
||||
this._sumZ += z
|
||||
}
|
||||
compute() {
|
||||
this._avgZ = Double.NaN
|
||||
if (this._numZ > 0) this._avgZ = this._sumZ / this._numZ
|
||||
}
|
||||
getZ() {
|
||||
return this._avgZ
|
||||
}
|
||||
}
|
||||
ElevationModel.ElevationCell = ElevationCell
|
||||
ElevationModel.DEFAULT_CELL_NUM = 3
|
||||
@ -1,45 +0,0 @@
|
||||
import OverlayNG from './OverlayNG.js'
|
||||
import OverlayUtil from './OverlayUtil.js'
|
||||
export default class FastOverlayFilter {
|
||||
constructor() {
|
||||
FastOverlayFilter.constructor_.apply(this, arguments)
|
||||
}
|
||||
static constructor_() {
|
||||
this._targetGeom = null
|
||||
this._isTargetRectangle = null
|
||||
const geom = arguments[0]
|
||||
this._targetGeom = geom
|
||||
this._isTargetRectangle = this._targetGeom.isRectangle()
|
||||
}
|
||||
createEmpty(geom) {
|
||||
return OverlayUtil.createEmptyResult(geom.getDimension(), geom.getFactory())
|
||||
}
|
||||
isEnvelopeCovers(a, b) {
|
||||
return a.getEnvelopeInternal().covers(b.getEnvelopeInternal())
|
||||
}
|
||||
intersection(geom) {
|
||||
const resultForRect = this.intersectionRectangle(geom)
|
||||
if (resultForRect !== null) return resultForRect
|
||||
if (!this.isEnvelopeIntersects(this._targetGeom, geom))
|
||||
return this.createEmpty(geom)
|
||||
|
||||
return null
|
||||
}
|
||||
overlay(geom, overlayOpCode) {
|
||||
if (overlayOpCode !== OverlayNG.INTERSECTION) return null
|
||||
return this.intersection(geom)
|
||||
}
|
||||
isEnvelopeIntersects(a, b) {
|
||||
return a.getEnvelopeInternal().intersects(b.getEnvelopeInternal())
|
||||
}
|
||||
intersectionRectangle(geom) {
|
||||
if (!this._isTargetRectangle) return null
|
||||
if (this.isEnvelopeCovers(this._targetGeom, geom))
|
||||
return geom.copy()
|
||||
|
||||
if (!this.isEnvelopeIntersects(this._targetGeom, geom))
|
||||
return this.createEmpty(geom)
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
import PointLocator from '../../algorithm/PointLocator.js'
|
||||
import PointOnGeometryLocator from '../../algorithm/locate/PointOnGeometryLocator.js'
|
||||
export default class IndexedPointOnLineLocator {
|
||||
constructor() {
|
||||
IndexedPointOnLineLocator.constructor_.apply(this, arguments)
|
||||
}
|
||||
static constructor_() {
|
||||
this._inputGeom = null
|
||||
const geomLinear = arguments[0]
|
||||
this._inputGeom = geomLinear
|
||||
}
|
||||
locate(p) {
|
||||
const locator = new PointLocator()
|
||||
return locator.locate(p, this._inputGeom)
|
||||
}
|
||||
get interfaces_() {
|
||||
return [PointOnGeometryLocator]
|
||||
}
|
||||
}
|
||||
@ -1,69 +0,0 @@
|
||||
import Location from '../../geom/Location.js'
|
||||
import IndexedPointInAreaLocator from '../../algorithm/locate/IndexedPointInAreaLocator.js'
|
||||
export default class InputGeometry {
|
||||
constructor() {
|
||||
InputGeometry.constructor_.apply(this, arguments)
|
||||
}
|
||||
static constructor_() {
|
||||
this._geom = new Array(2).fill(null)
|
||||
this._ptLocatorA = null
|
||||
this._ptLocatorB = null
|
||||
this._isCollapsed = new Array(2).fill(null)
|
||||
const geomA = arguments[0], geomB = arguments[1]
|
||||
this._geom = [geomA, geomB]
|
||||
}
|
||||
hasPoints() {
|
||||
return this.getDimension(0) === 0 || this.getDimension(1) === 0
|
||||
}
|
||||
isAllPoints() {
|
||||
return this.getDimension(0) === 0 && this._geom[1] !== null && this.getDimension(1) === 0
|
||||
}
|
||||
getGeometry(geomIndex) {
|
||||
return this._geom[geomIndex]
|
||||
}
|
||||
isSingle() {
|
||||
return this._geom[1] === null
|
||||
}
|
||||
isLine(geomIndex) {
|
||||
return this.getDimension(geomIndex) === 1
|
||||
}
|
||||
getDimension(index) {
|
||||
if (this._geom[index] === null) return -1
|
||||
return this._geom[index].getDimension()
|
||||
}
|
||||
getEnvelope(geomIndex) {
|
||||
return this._geom[geomIndex].getEnvelopeInternal()
|
||||
}
|
||||
setCollapsed(geomIndex, isGeomCollapsed) {
|
||||
this._isCollapsed[geomIndex] = isGeomCollapsed
|
||||
}
|
||||
getAreaIndex() {
|
||||
if (this.getDimension(0) === 2) return 0
|
||||
if (this.getDimension(1) === 2) return 1
|
||||
return -1
|
||||
}
|
||||
getLocator(geomIndex) {
|
||||
if (geomIndex === 0) {
|
||||
if (this._ptLocatorA === null) this._ptLocatorA = new IndexedPointInAreaLocator(this.getGeometry(geomIndex))
|
||||
return this._ptLocatorA
|
||||
} else {
|
||||
if (this._ptLocatorB === null) this._ptLocatorB = new IndexedPointInAreaLocator(this.getGeometry(geomIndex))
|
||||
return this._ptLocatorB
|
||||
}
|
||||
}
|
||||
hasEdges(geomIndex) {
|
||||
return this._geom[geomIndex] !== null && this._geom[geomIndex].getDimension() > 0
|
||||
}
|
||||
locatePointInArea(geomIndex, pt) {
|
||||
if (this._isCollapsed[geomIndex]) return Location.EXTERIOR
|
||||
if (this.getGeometry(geomIndex).isEmpty() || this._isCollapsed[geomIndex]) return Location.EXTERIOR
|
||||
const ptLocator = this.getLocator(geomIndex)
|
||||
return ptLocator.locate(pt)
|
||||
}
|
||||
isArea(geomIndex) {
|
||||
return this._geom[geomIndex] !== null && this._geom[geomIndex].getDimension() === 2
|
||||
}
|
||||
isEmpty(geomIndex) {
|
||||
return this._geom[geomIndex].isEmpty()
|
||||
}
|
||||
}
|
||||
@ -1,49 +0,0 @@
|
||||
import OverlayNG from './OverlayNG.js'
|
||||
import ArrayList from '../../../../../java/util/ArrayList.js'
|
||||
export default class IntersectionPointBuilder {
|
||||
constructor() {
|
||||
IntersectionPointBuilder.constructor_.apply(this, arguments)
|
||||
}
|
||||
static constructor_() {
|
||||
this._geometryFactory = null
|
||||
this._graph = null
|
||||
this._points = new ArrayList()
|
||||
this._isAllowCollapseLines = !OverlayNG.STRICT_MODE_DEFAULT
|
||||
const graph = arguments[0], geomFact = arguments[1]
|
||||
this._graph = graph
|
||||
this._geometryFactory = geomFact
|
||||
}
|
||||
getPoints() {
|
||||
this.addResultPoints()
|
||||
return this._points
|
||||
}
|
||||
addResultPoints() {
|
||||
for (const nodeEdge of this._graph.getNodeEdges())
|
||||
if (this.isResultPoint(nodeEdge)) {
|
||||
const pt = this._geometryFactory.createPoint(nodeEdge.getCoordinate().copy())
|
||||
this._points.add(pt)
|
||||
}
|
||||
|
||||
}
|
||||
isEdgeOf(label, i) {
|
||||
if (!this._isAllowCollapseLines && label.isBoundaryCollapse()) return false
|
||||
return label.isBoundary(i) || label.isLine(i)
|
||||
}
|
||||
isResultPoint(nodeEdge) {
|
||||
let isEdgeOfA = false
|
||||
let isEdgeOfB = false
|
||||
let edge = nodeEdge
|
||||
do {
|
||||
if (edge.isInResult()) return false
|
||||
const label = edge.getLabel()
|
||||
isEdgeOfA |= this.isEdgeOf(label, 0)
|
||||
isEdgeOfB |= this.isEdgeOf(label, 1)
|
||||
edge = edge.oNext()
|
||||
} while (edge !== nodeEdge)
|
||||
const isNodeInBoth = isEdgeOfA && isEdgeOfB
|
||||
return isNodeInBoth
|
||||
}
|
||||
setStrictMode(isStrictMode) {
|
||||
this._isAllowCollapseLines = !isStrictMode
|
||||
}
|
||||
}
|
||||
@ -1,143 +0,0 @@
|
||||
import Location from '../../geom/Location.js'
|
||||
import CoordinateList from '../../geom/CoordinateList.js'
|
||||
import OverlayNG from './OverlayNG.js'
|
||||
import ArrayList from '../../../../../java/util/ArrayList.js'
|
||||
export default class LineBuilder {
|
||||
constructor() {
|
||||
LineBuilder.constructor_.apply(this, arguments)
|
||||
}
|
||||
static constructor_() {
|
||||
this._geometryFactory = null
|
||||
this._graph = null
|
||||
this._opCode = null
|
||||
this._inputAreaIndex = null
|
||||
this._hasResultArea = null
|
||||
this._isAllowMixedResult = !OverlayNG.STRICT_MODE_DEFAULT
|
||||
this._isAllowCollapseLines = !OverlayNG.STRICT_MODE_DEFAULT
|
||||
this._lines = new ArrayList()
|
||||
const inputGeom = arguments[0], graph = arguments[1], hasResultArea = arguments[2], opCode = arguments[3], geomFact = arguments[4]
|
||||
this._graph = graph
|
||||
this._opCode = opCode
|
||||
this._geometryFactory = geomFact
|
||||
this._hasResultArea = hasResultArea
|
||||
this._inputAreaIndex = inputGeom.getAreaIndex()
|
||||
}
|
||||
static effectiveLocation(lbl, geomIndex) {
|
||||
if (lbl.isCollapse(geomIndex)) return Location.INTERIOR
|
||||
if (lbl.isLine(geomIndex)) return Location.INTERIOR
|
||||
return lbl.getLineLocation(geomIndex)
|
||||
}
|
||||
static nextLineEdgeUnvisited(node) {
|
||||
let e = node
|
||||
do {
|
||||
e = e.oNextOE()
|
||||
if (e.isVisited()) continue
|
||||
if (e.isInResultLine())
|
||||
return e
|
||||
|
||||
} while (e !== node)
|
||||
return null
|
||||
}
|
||||
static degreeOfLines(node) {
|
||||
let degree = 0
|
||||
let e = node
|
||||
do {
|
||||
if (e.isInResultLine())
|
||||
degree++
|
||||
|
||||
e = e.oNextOE()
|
||||
} while (e !== node)
|
||||
return degree
|
||||
}
|
||||
isResultLine(lbl) {
|
||||
if (lbl.isBoundarySingleton()) return false
|
||||
if (!this._isAllowCollapseLines && lbl.isBoundaryCollapse()) return false
|
||||
if (lbl.isInteriorCollapse()) return false
|
||||
if (this._opCode !== OverlayNG.INTERSECTION) {
|
||||
if (lbl.isCollapseAndNotPartInterior()) return false
|
||||
if (this._hasResultArea && lbl.isLineInArea(this._inputAreaIndex)) return false
|
||||
}
|
||||
if (this._isAllowMixedResult && this._opCode === OverlayNG.INTERSECTION && lbl.isBoundaryTouch())
|
||||
return true
|
||||
|
||||
const aLoc = LineBuilder.effectiveLocation(lbl, 0)
|
||||
const bLoc = LineBuilder.effectiveLocation(lbl, 1)
|
||||
const isInResult = OverlayNG.isResultOfOp(this._opCode, aLoc, bLoc)
|
||||
return isInResult
|
||||
}
|
||||
getLines() {
|
||||
this.markResultLines()
|
||||
this.addResultLines()
|
||||
return this._lines
|
||||
}
|
||||
buildLine(node) {
|
||||
const pts = new CoordinateList()
|
||||
pts.add(node.orig(), false)
|
||||
const isForward = node.isForward()
|
||||
let e = node
|
||||
do {
|
||||
e.markVisitedBoth()
|
||||
e.addCoordinates(pts)
|
||||
if (LineBuilder.degreeOfLines(e.symOE()) !== 2)
|
||||
break
|
||||
|
||||
e = LineBuilder.nextLineEdgeUnvisited(e.symOE())
|
||||
} while (e !== null)
|
||||
const ptsOut = pts.toCoordinateArray(isForward)
|
||||
const line = this._geometryFactory.createLineString(ptsOut)
|
||||
return line
|
||||
}
|
||||
addResultLinesRings() {
|
||||
const edges = this._graph.getEdges()
|
||||
for (const edge of edges) {
|
||||
if (!edge.isInResultLine()) continue
|
||||
if (edge.isVisited()) continue
|
||||
this._lines.add(this.buildLine(edge))
|
||||
}
|
||||
}
|
||||
addResultLinesForNodes() {
|
||||
const edges = this._graph.getEdges()
|
||||
for (const edge of edges) {
|
||||
if (!edge.isInResultLine()) continue
|
||||
if (edge.isVisited()) continue
|
||||
if (LineBuilder.degreeOfLines(edge) !== 2)
|
||||
this._lines.add(this.buildLine(edge))
|
||||
|
||||
}
|
||||
}
|
||||
markResultLines() {
|
||||
const edges = this._graph.getEdges()
|
||||
for (const edge of edges) {
|
||||
if (edge.isInResultEither()) continue
|
||||
if (this.isResultLine(edge.getLabel()))
|
||||
edge.markInResultLine()
|
||||
|
||||
}
|
||||
}
|
||||
addResultLines() {
|
||||
const edges = this._graph.getEdges()
|
||||
for (const edge of edges) {
|
||||
if (!edge.isInResultLine()) continue
|
||||
if (edge.isVisited()) continue
|
||||
this._lines.add(this.toLine(edge))
|
||||
edge.markVisitedBoth()
|
||||
}
|
||||
}
|
||||
setStrictMode(isStrictResultMode) {
|
||||
this._isAllowCollapseLines = !isStrictResultMode
|
||||
this._isAllowMixedResult = !isStrictResultMode
|
||||
}
|
||||
addResultLinesMerged() {
|
||||
this.addResultLinesForNodes()
|
||||
this.addResultLinesRings()
|
||||
}
|
||||
toLine(edge) {
|
||||
const isForward = edge.isForward()
|
||||
const pts = new CoordinateList()
|
||||
pts.add(edge.orig(), false)
|
||||
edge.addCoordinates(pts)
|
||||
const ptsOut = pts.toCoordinateArray(isForward)
|
||||
const line = this._geometryFactory.createLineString(ptsOut)
|
||||
return line
|
||||
}
|
||||
}
|
||||
@ -1,72 +0,0 @@
|
||||
import CoordinateList from '../../geom/CoordinateList.js'
|
||||
import ArrayList from '../../../../../java/util/ArrayList.js'
|
||||
export default class LineLimiter {
|
||||
constructor() {
|
||||
LineLimiter.constructor_.apply(this, arguments)
|
||||
}
|
||||
static constructor_() {
|
||||
this._limitEnv = null
|
||||
this._ptList = null
|
||||
this._lastOutside = null
|
||||
this._sections = null
|
||||
const env = arguments[0]
|
||||
this._limitEnv = env
|
||||
}
|
||||
isSectionOpen() {
|
||||
return this._ptList !== null
|
||||
}
|
||||
addPoint(p) {
|
||||
if (p === null) return null
|
||||
this.startSection()
|
||||
this._ptList.add(p, false)
|
||||
}
|
||||
addOutside(p) {
|
||||
const segIntersects = this.isLastSegmentIntersecting(p)
|
||||
if (!segIntersects) {
|
||||
this.finishSection()
|
||||
} else {
|
||||
this.addPoint(this._lastOutside)
|
||||
this.addPoint(p)
|
||||
}
|
||||
this._lastOutside = p
|
||||
}
|
||||
finishSection() {
|
||||
if (this._ptList === null) return null
|
||||
if (this._lastOutside !== null) {
|
||||
this._ptList.add(this._lastOutside, false)
|
||||
this._lastOutside = null
|
||||
}
|
||||
const section = this._ptList.toCoordinateArray()
|
||||
this._sections.add(section)
|
||||
this._ptList = null
|
||||
}
|
||||
startSection() {
|
||||
if (this._ptList === null)
|
||||
this._ptList = new CoordinateList()
|
||||
|
||||
if (this._lastOutside !== null)
|
||||
this._ptList.add(this._lastOutside, false)
|
||||
|
||||
this._lastOutside = null
|
||||
}
|
||||
limit(pts) {
|
||||
this._lastOutside = null
|
||||
this._ptList = null
|
||||
this._sections = new ArrayList()
|
||||
for (let i = 0; i < pts.length; i++) {
|
||||
const p = pts[i]
|
||||
if (this._limitEnv.intersects(p)) this.addPoint(p); else
|
||||
this.addOutside(p)
|
||||
|
||||
}
|
||||
this.finishSection()
|
||||
return this._sections
|
||||
}
|
||||
isLastSegmentIntersecting(p) {
|
||||
if (this._lastOutside === null) {
|
||||
if (this.isSectionOpen()) return true
|
||||
return false
|
||||
}
|
||||
return this._limitEnv.intersects(this._lastOutside, p)
|
||||
}
|
||||
}
|
||||
@ -1,126 +0,0 @@
|
||||
import CoordinateList from '../../geom/CoordinateList.js'
|
||||
import WKTWriter from '../../io/WKTWriter.js'
|
||||
import TopologyException from '../../geom/TopologyException.js'
|
||||
import OverlayEdgeRing from './OverlayEdgeRing.js'
|
||||
import ArrayList from '../../../../../java/util/ArrayList.js'
|
||||
import Assert from '../../util/Assert.js'
|
||||
export default class MaximalEdgeRing {
|
||||
constructor() {
|
||||
MaximalEdgeRing.constructor_.apply(this, arguments)
|
||||
}
|
||||
static constructor_() {
|
||||
this._startEdge = null
|
||||
const e = arguments[0]
|
||||
this._startEdge = e
|
||||
this.attachEdges(e)
|
||||
}
|
||||
static isAlreadyLinked(edge, maxRing) {
|
||||
const isLinked = edge.getEdgeRingMax() === maxRing && edge.isResultLinked()
|
||||
return isLinked
|
||||
}
|
||||
static linkMaxInEdge(currOut, currMaxRingOut, maxEdgeRing) {
|
||||
const currIn = currOut.symOE()
|
||||
if (currIn.getEdgeRingMax() !== maxEdgeRing) return currMaxRingOut
|
||||
currIn.setNextResult(currMaxRingOut)
|
||||
return null
|
||||
}
|
||||
static linkResultAreaMaxRingAtNode(nodeEdge) {
|
||||
Assert.isTrue(nodeEdge.isInResultArea(), 'Attempt to link non-result edge')
|
||||
const endOut = nodeEdge.oNextOE()
|
||||
let currOut = endOut
|
||||
let state = MaximalEdgeRing.STATE_FIND_INCOMING
|
||||
let currResultIn = null
|
||||
do {
|
||||
if (currResultIn !== null && currResultIn.isResultMaxLinked()) return null
|
||||
switch (state) {
|
||||
case MaximalEdgeRing.STATE_FIND_INCOMING:
|
||||
const currIn = currOut.symOE()
|
||||
if (!currIn.isInResultArea()) break
|
||||
currResultIn = currIn
|
||||
state = MaximalEdgeRing.STATE_LINK_OUTGOING
|
||||
break
|
||||
case MaximalEdgeRing.STATE_LINK_OUTGOING:
|
||||
if (!currOut.isInResultArea()) break
|
||||
currResultIn.setNextResultMax(currOut)
|
||||
state = MaximalEdgeRing.STATE_FIND_INCOMING
|
||||
break
|
||||
}
|
||||
currOut = currOut.oNextOE()
|
||||
} while (currOut !== endOut)
|
||||
if (state === MaximalEdgeRing.STATE_LINK_OUTGOING)
|
||||
throw new TopologyException('no outgoing edge found', nodeEdge.getCoordinate())
|
||||
|
||||
}
|
||||
static linkMinRingEdgesAtNode(nodeEdge, maxRing) {
|
||||
const endOut = nodeEdge
|
||||
let currMaxRingOut = endOut
|
||||
let currOut = endOut.oNextOE()
|
||||
do {
|
||||
if (MaximalEdgeRing.isAlreadyLinked(currOut.symOE(), maxRing)) return null
|
||||
if (currMaxRingOut === null)
|
||||
currMaxRingOut = MaximalEdgeRing.selectMaxOutEdge(currOut, maxRing)
|
||||
else
|
||||
currMaxRingOut = MaximalEdgeRing.linkMaxInEdge(currOut, currMaxRingOut, maxRing)
|
||||
|
||||
currOut = currOut.oNextOE()
|
||||
} while (currOut !== endOut)
|
||||
if (currMaxRingOut !== null)
|
||||
throw new TopologyException('Unmatched edge found during min-ring linking', nodeEdge.getCoordinate())
|
||||
|
||||
}
|
||||
static selectMaxOutEdge(currOut, maxEdgeRing) {
|
||||
if (currOut.getEdgeRingMax() === maxEdgeRing) return currOut
|
||||
return null
|
||||
}
|
||||
getCoordinates() {
|
||||
const coords = new CoordinateList()
|
||||
let edge = this._startEdge
|
||||
do {
|
||||
coords.add(edge.orig())
|
||||
if (edge.nextResultMax() === null)
|
||||
break
|
||||
|
||||
edge = edge.nextResultMax()
|
||||
} while (edge !== this._startEdge)
|
||||
coords.add(edge.dest())
|
||||
return coords.toCoordinateArray()
|
||||
}
|
||||
linkMinimalRings() {
|
||||
let e = this._startEdge
|
||||
do {
|
||||
MaximalEdgeRing.linkMinRingEdgesAtNode(e, this)
|
||||
e = e.nextResultMax()
|
||||
} while (e !== this._startEdge)
|
||||
}
|
||||
buildMinimalRings(geometryFactory) {
|
||||
this.linkMinimalRings()
|
||||
const minEdgeRings = new ArrayList()
|
||||
let e = this._startEdge
|
||||
do {
|
||||
if (e.getEdgeRing() === null) {
|
||||
const minEr = new OverlayEdgeRing(e, geometryFactory)
|
||||
minEdgeRings.add(minEr)
|
||||
}
|
||||
e = e.nextResultMax()
|
||||
} while (e !== this._startEdge)
|
||||
return minEdgeRings
|
||||
}
|
||||
attachEdges(startEdge) {
|
||||
let edge = startEdge
|
||||
do {
|
||||
if (edge === null) throw new TopologyException('Ring edge is null')
|
||||
if (edge.getEdgeRingMax() === this) throw new TopologyException('Ring edge visited twice at ' + edge.getCoordinate(), edge.getCoordinate())
|
||||
if (edge.nextResultMax() === null)
|
||||
throw new TopologyException('Ring edge missing at', edge.dest())
|
||||
|
||||
edge.setEdgeRingMax(this)
|
||||
edge = edge.nextResultMax()
|
||||
} while (edge !== startEdge)
|
||||
}
|
||||
toString() {
|
||||
const pts = this.getCoordinates()
|
||||
return WKTWriter.toLineString(pts)
|
||||
}
|
||||
}
|
||||
MaximalEdgeRing.STATE_FIND_INCOMING = 1
|
||||
MaximalEdgeRing.STATE_LINK_OUTGOING = 2
|
||||
@ -1,186 +0,0 @@
|
||||
import WKTWriter from '../../io/WKTWriter.js';
|
||||
import HalfEdge from '../../edgegraph/HalfEdge.js';
|
||||
import CoordinateArrays from '../../geom/CoordinateArrays.js';
|
||||
export default class OverlayEdge extends HalfEdge {
|
||||
constructor() {
|
||||
super();
|
||||
OverlayEdge.constructor_.apply(this, arguments);
|
||||
}
|
||||
static constructor_() {
|
||||
this._pts = null;
|
||||
this._direction = null;
|
||||
this._dirPt = null;
|
||||
this._label = null;
|
||||
this._isInResultArea = false;
|
||||
this._isInResultLine = false;
|
||||
this._isVisited = false;
|
||||
this._nextResultEdge = null;
|
||||
this._edgeRing = null;
|
||||
this._maxEdgeRing = null;
|
||||
this._nextResultMaxEdge = null;
|
||||
let orig = arguments[0], dirPt = arguments[1], direction = arguments[2], label = arguments[3], pts = arguments[4];
|
||||
HalfEdge.constructor_.call(this, orig);
|
||||
this._dirPt = dirPt;
|
||||
this._direction = direction;
|
||||
this._pts = pts;
|
||||
this._label = label;
|
||||
}
|
||||
static createEdge(pts, lbl, direction) {
|
||||
let origin = null;
|
||||
let dirPt = null;
|
||||
if (direction) {
|
||||
origin = pts[0];
|
||||
dirPt = pts[1];
|
||||
} else {
|
||||
let ilast = pts.length - 1;
|
||||
origin = pts[ilast];
|
||||
dirPt = pts[ilast - 1];
|
||||
}
|
||||
return new OverlayEdge(origin, dirPt, direction, lbl, pts);
|
||||
}
|
||||
static createEdgePair(pts, lbl) {
|
||||
let e0 = OverlayEdge.createEdge(pts, lbl, true);
|
||||
let e1 = OverlayEdge.createEdge(pts, lbl, false);
|
||||
e0.link(e1);
|
||||
return e0;
|
||||
}
|
||||
static nodeComparator() {
|
||||
return new (class {
|
||||
get interfaces_() {
|
||||
return [Comparator<OverlayEdge>];
|
||||
}
|
||||
compare(e1, e2) {
|
||||
return e1.orig().compareTo(e2.orig());
|
||||
}
|
||||
})();
|
||||
}
|
||||
symOE() {
|
||||
return this.sym();
|
||||
}
|
||||
markInResultArea() {
|
||||
this._isInResultArea = true;
|
||||
}
|
||||
getCoordinates() {
|
||||
return this._pts;
|
||||
}
|
||||
nextResultMax() {
|
||||
return this._nextResultMaxEdge;
|
||||
}
|
||||
resultSymbol() {
|
||||
if (this._isInResultArea) return " resA";
|
||||
if (this._isInResultLine) return " resL";
|
||||
return "";
|
||||
}
|
||||
isInResultLine() {
|
||||
return this._isInResultLine;
|
||||
}
|
||||
getCoordinate() {
|
||||
return this.orig();
|
||||
}
|
||||
isInResultAreaBoth() {
|
||||
return this._isInResultArea && this.symOE()._isInResultArea;
|
||||
}
|
||||
directionPt() {
|
||||
return this._dirPt;
|
||||
}
|
||||
addCoordinates(coords) {
|
||||
let isFirstEdge = coords.size() > 0;
|
||||
if (this._direction) {
|
||||
let startIndex = 1;
|
||||
if (isFirstEdge) startIndex = 0;
|
||||
for (let i = startIndex; i < this._pts.length; i++) {
|
||||
coords.add(this._pts[i], false);
|
||||
}
|
||||
} else {
|
||||
let startIndex = this._pts.length - 2;
|
||||
if (isFirstEdge) startIndex = this._pts.length - 1;
|
||||
for (let i = startIndex; i >= 0; i--) {
|
||||
coords.add(this._pts[i], false);
|
||||
}
|
||||
}
|
||||
}
|
||||
nextResult() {
|
||||
return this._nextResultEdge;
|
||||
}
|
||||
getCoordinatesOriented() {
|
||||
if (this._direction) {
|
||||
return this._pts;
|
||||
}
|
||||
let copy = this._pts.clone();
|
||||
CoordinateArrays.reverse(copy);
|
||||
return copy;
|
||||
}
|
||||
setNextResultMax(e) {
|
||||
this._nextResultMaxEdge = e;
|
||||
}
|
||||
setEdgeRing(edgeRing) {
|
||||
this._edgeRing = edgeRing;
|
||||
}
|
||||
isInResultEither() {
|
||||
return this.isInResult() || this.symOE().isInResult();
|
||||
}
|
||||
getEdgeRingMax() {
|
||||
return this._maxEdgeRing;
|
||||
}
|
||||
isResultLinked() {
|
||||
return this._nextResultEdge !== null;
|
||||
}
|
||||
isForward() {
|
||||
return this._direction;
|
||||
}
|
||||
setEdgeRingMax(maximalEdgeRing) {
|
||||
this._maxEdgeRing = maximalEdgeRing;
|
||||
}
|
||||
unmarkFromResultAreaBoth() {
|
||||
this._isInResultArea = false;
|
||||
this.symOE()._isInResultArea = false;
|
||||
}
|
||||
markInResultLine() {
|
||||
this._isInResultLine = true;
|
||||
this.symOE()._isInResultLine = true;
|
||||
}
|
||||
getLabel() {
|
||||
return this._label;
|
||||
}
|
||||
markVisitedBoth() {
|
||||
this.markVisited();
|
||||
this.symOE().markVisited();
|
||||
}
|
||||
isResultMaxLinked() {
|
||||
return this._nextResultMaxEdge !== null;
|
||||
}
|
||||
getLocation(index, position) {
|
||||
return this._label.getLocation(index, position, this._direction);
|
||||
}
|
||||
markVisited() {
|
||||
this._isVisited = true;
|
||||
}
|
||||
oNextOE() {
|
||||
return this.oNext();
|
||||
}
|
||||
setNextResult(e) {
|
||||
this._nextResultEdge = e;
|
||||
}
|
||||
toString() {
|
||||
let orig = this.orig();
|
||||
let dest = this.dest();
|
||||
let dirPtStr = this._pts.length > 2 ? ", " + WKTWriter.format(this.directionPt()) : "";
|
||||
return "OE( " + WKTWriter.format(orig) + dirPtStr + " .. " + WKTWriter.format(dest) + " ) " + this._label.toString(this._direction) + this.resultSymbol() + " / Sym: " + this.symOE().getLabel().toString(this.symOE()._direction) + this.symOE().resultSymbol();
|
||||
}
|
||||
getEdgeRing() {
|
||||
return this._edgeRing;
|
||||
}
|
||||
isInResultArea() {
|
||||
return this._isInResultArea;
|
||||
}
|
||||
isInResult() {
|
||||
return this._isInResultArea || this._isInResultLine;
|
||||
}
|
||||
isVisited() {
|
||||
return this._isVisited;
|
||||
}
|
||||
markInResultAreaBoth() {
|
||||
this._isInResultArea = true;
|
||||
this.symOE()._isInResultArea = true;
|
||||
}
|
||||
}
|
||||
@ -1,123 +0,0 @@
|
||||
import Location from '../../geom/Location.js'
|
||||
import CoordinateList from '../../geom/CoordinateList.js'
|
||||
import TopologyException from '../../geom/TopologyException.js'
|
||||
import Orientation from '../../algorithm/Orientation.js'
|
||||
import ArrayList from '../../../../../java/util/ArrayList.js'
|
||||
import IndexedPointInAreaLocator from '../../algorithm/locate/IndexedPointInAreaLocator.js'
|
||||
export default class OverlayEdgeRing {
|
||||
constructor() {
|
||||
OverlayEdgeRing.constructor_.apply(this, arguments)
|
||||
}
|
||||
static constructor_() {
|
||||
this._startEdge = null
|
||||
this._ring = null
|
||||
this._isHole = null
|
||||
this._ringPts = null
|
||||
this._locator = null
|
||||
this._shell = null
|
||||
this._holes = new ArrayList()
|
||||
const start = arguments[0], geometryFactory = arguments[1]
|
||||
this._startEdge = start
|
||||
this._ringPts = this.computeRingPts(start)
|
||||
this.computeRing(this._ringPts, geometryFactory)
|
||||
}
|
||||
computeRing(ringPts, geometryFactory) {
|
||||
if (this._ring !== null) return null
|
||||
this._ring = geometryFactory.createLinearRing(ringPts)
|
||||
this._isHole = Orientation.isCCW(this._ring.getCoordinates())
|
||||
}
|
||||
getCoordinates() {
|
||||
return this._ringPts
|
||||
}
|
||||
isPointInOrOut(ring) {
|
||||
for (const pt of ring.getCoordinates()) {
|
||||
const loc = this.locate(pt)
|
||||
if (loc === Location.INTERIOR)
|
||||
return true
|
||||
|
||||
if (loc === Location.EXTERIOR)
|
||||
return false
|
||||
|
||||
}
|
||||
return false
|
||||
}
|
||||
getCoordinate() {
|
||||
return this._ringPts[0]
|
||||
}
|
||||
isHole() {
|
||||
return this._isHole
|
||||
}
|
||||
addHole(ring) {
|
||||
this._holes.add(ring)
|
||||
}
|
||||
getEnvelope() {
|
||||
return this._ring.getEnvelopeInternal()
|
||||
}
|
||||
getEdge() {
|
||||
return this._startEdge
|
||||
}
|
||||
computeRingPts(start) {
|
||||
let edge = start
|
||||
const pts = new CoordinateList()
|
||||
do {
|
||||
if (edge.getEdgeRing() === this) throw new TopologyException('Edge visited twice during ring-building at ' + edge.getCoordinate(), edge.getCoordinate())
|
||||
edge.addCoordinates(pts)
|
||||
edge.setEdgeRing(this)
|
||||
if (edge.nextResult() === null) throw new TopologyException('Found null edge in ring', edge.dest())
|
||||
edge = edge.nextResult()
|
||||
} while (edge !== start)
|
||||
pts.closeRing()
|
||||
return pts.toCoordinateArray()
|
||||
}
|
||||
hasShell() {
|
||||
return this._shell !== null
|
||||
}
|
||||
findEdgeRingContaining(erList) {
|
||||
let minContainingRing = null
|
||||
for (const edgeRing of erList)
|
||||
if (edgeRing.contains(this))
|
||||
if (minContainingRing === null || minContainingRing.getEnvelope().contains(edgeRing.getEnvelope()))
|
||||
minContainingRing = edgeRing
|
||||
|
||||
|
||||
|
||||
return minContainingRing
|
||||
}
|
||||
getLocator() {
|
||||
if (this._locator === null)
|
||||
this._locator = new IndexedPointInAreaLocator(this.getRing())
|
||||
|
||||
return this._locator
|
||||
}
|
||||
getShell() {
|
||||
if (this.isHole()) return this._shell
|
||||
return this
|
||||
}
|
||||
contains(ring) {
|
||||
const env = this.getEnvelope()
|
||||
const testEnv = ring.getEnvelope()
|
||||
if (!env.containsProperly(testEnv)) return false
|
||||
return this.isPointInOrOut(ring)
|
||||
}
|
||||
getRing() {
|
||||
return this._ring
|
||||
}
|
||||
locate(pt) {
|
||||
return this.getLocator().locate(pt)
|
||||
}
|
||||
setShell(shell) {
|
||||
this._shell = shell
|
||||
if (shell !== null) shell.addHole(this)
|
||||
}
|
||||
toPolygon(factory) {
|
||||
let holeLR = null
|
||||
if (this._holes !== null) {
|
||||
holeLR = new Array(this._holes.size()).fill(null)
|
||||
for (let i = 0; i < this._holes.size(); i++)
|
||||
holeLR[i] = this._holes.get(i).getRing()
|
||||
|
||||
}
|
||||
const poly = factory.createPolygon(this._ring, holeLR)
|
||||
return poly
|
||||
}
|
||||
}
|
||||
@ -1,45 +0,0 @@
|
||||
import HashMap from '../../../../../java/util/HashMap.js'
|
||||
import OverlayEdge from './OverlayEdge.js'
|
||||
import ArrayList from '../../../../../java/util/ArrayList.js'
|
||||
export default class OverlayGraph {
|
||||
constructor() {
|
||||
OverlayGraph.constructor_.apply(this, arguments)
|
||||
}
|
||||
static constructor_() {
|
||||
this._edges = new ArrayList()
|
||||
this._nodeMap = new HashMap()
|
||||
}
|
||||
getNodeEdge(nodePt) {
|
||||
return this._nodeMap.get(nodePt)
|
||||
}
|
||||
insert(e) {
|
||||
this._edges.add(e)
|
||||
const nodeEdge = this._nodeMap.get(e.orig())
|
||||
if (nodeEdge !== null)
|
||||
nodeEdge.insert(e)
|
||||
else
|
||||
this._nodeMap.put(e.orig(), e)
|
||||
|
||||
}
|
||||
getResultAreaEdges() {
|
||||
const resultEdges = new ArrayList()
|
||||
for (const edge of this.getEdges())
|
||||
if (edge.isInResultArea())
|
||||
resultEdges.add(edge)
|
||||
|
||||
|
||||
return resultEdges
|
||||
}
|
||||
addEdge(pts, label) {
|
||||
const e = OverlayEdge.createEdgePair(pts, label)
|
||||
this.insert(e)
|
||||
this.insert(e.symOE())
|
||||
return e
|
||||
}
|
||||
getEdges() {
|
||||
return this._edges
|
||||
}
|
||||
getNodeEdges() {
|
||||
return this._nodeMap.values()
|
||||
}
|
||||
}
|
||||
@ -1,306 +0,0 @@
|
||||
import Location from '../../geom/Location.js'
|
||||
import Position from '../../geom/Position.js'
|
||||
import StringBuilder from '../../../../../java/lang/StringBuilder.js'
|
||||
export default class OverlayLabel {
|
||||
constructor() {
|
||||
OverlayLabel.constructor_.apply(this, arguments)
|
||||
}
|
||||
static constructor_() {
|
||||
this._aDim = OverlayLabel.DIM_NOT_PART
|
||||
this._aIsHole = false
|
||||
this._aLocLeft = OverlayLabel.LOC_UNKNOWN
|
||||
this._aLocRight = OverlayLabel.LOC_UNKNOWN
|
||||
this._aLocLine = OverlayLabel.LOC_UNKNOWN
|
||||
this._bDim = OverlayLabel.DIM_NOT_PART
|
||||
this._bIsHole = false
|
||||
this._bLocLeft = OverlayLabel.LOC_UNKNOWN
|
||||
this._bLocRight = OverlayLabel.LOC_UNKNOWN
|
||||
this._bLocLine = OverlayLabel.LOC_UNKNOWN
|
||||
if (arguments.length === 0) {} else if (arguments.length === 1) {
|
||||
if (Number.isInteger(arguments[0])) {
|
||||
const index = arguments[0]
|
||||
this.initLine(index)
|
||||
} else if (arguments[0] instanceof OverlayLabel) {
|
||||
const lbl = arguments[0]
|
||||
this._aLocLeft = lbl._aLocLeft
|
||||
this._aLocRight = lbl._aLocRight
|
||||
this._aLocLine = lbl._aLocLine
|
||||
this._aDim = lbl._aDim
|
||||
this._aIsHole = lbl._aIsHole
|
||||
this._bLocLeft = lbl._bLocLeft
|
||||
this._bLocRight = lbl._bLocRight
|
||||
this._bLocLine = lbl._bLocLine
|
||||
this._bDim = lbl._bDim
|
||||
this._bIsHole = lbl._bIsHole
|
||||
}
|
||||
} else if (arguments.length === 4) {
|
||||
const index = arguments[0], locLeft = arguments[1], locRight = arguments[2], isHole = arguments[3]
|
||||
this.initBoundary(index, locLeft, locRight, isHole)
|
||||
}
|
||||
}
|
||||
static ringRoleSymbol(isHole) {
|
||||
return isHole ? 'h' : 's'
|
||||
}
|
||||
static dimensionSymbol(dim) {
|
||||
switch (dim) {
|
||||
case OverlayLabel.DIM_LINE:
|
||||
return OverlayLabel.SYM_LINE
|
||||
case OverlayLabel.DIM_COLLAPSE:
|
||||
return OverlayLabel.SYM_COLLAPSE
|
||||
case OverlayLabel.DIM_BOUNDARY:
|
||||
return OverlayLabel.SYM_BOUNDARY
|
||||
}
|
||||
return OverlayLabel.SYM_UNKNOWN
|
||||
}
|
||||
isKnown(index) {
|
||||
if (index === 0)
|
||||
return this._aDim !== OverlayLabel.DIM_UNKNOWN
|
||||
|
||||
return this._bDim !== OverlayLabel.DIM_UNKNOWN
|
||||
}
|
||||
isLineInArea(index) {
|
||||
if (index === 0)
|
||||
return this._aLocLine === Location.INTERIOR
|
||||
|
||||
return this._bLocLine === Location.INTERIOR
|
||||
}
|
||||
isNotPart(index) {
|
||||
if (index === 0)
|
||||
return this._aDim === OverlayLabel.DIM_NOT_PART
|
||||
|
||||
return this._bDim === OverlayLabel.DIM_NOT_PART
|
||||
}
|
||||
isBoundaryEither() {
|
||||
return this._aDim === OverlayLabel.DIM_BOUNDARY || this._bDim === OverlayLabel.DIM_BOUNDARY
|
||||
}
|
||||
initCollapse(index, isHole) {
|
||||
if (index === 0) {
|
||||
this._aDim = OverlayLabel.DIM_COLLAPSE
|
||||
this._aIsHole = isHole
|
||||
} else {
|
||||
this._bDim = OverlayLabel.DIM_COLLAPSE
|
||||
this._bIsHole = isHole
|
||||
}
|
||||
}
|
||||
getLocationBoundaryOrLine(index, position, isForward) {
|
||||
if (this.isBoundary(index))
|
||||
return this.getLocation(index, position, isForward)
|
||||
|
||||
return this.getLineLocation(index)
|
||||
}
|
||||
isBoundary(index) {
|
||||
if (index === 0)
|
||||
return this._aDim === OverlayLabel.DIM_BOUNDARY
|
||||
|
||||
return this._bDim === OverlayLabel.DIM_BOUNDARY
|
||||
}
|
||||
isInteriorCollapse() {
|
||||
if (this._aDim === OverlayLabel.DIM_COLLAPSE && this._aLocLine === Location.INTERIOR) return true
|
||||
if (this._bDim === OverlayLabel.DIM_COLLAPSE && this._bLocLine === Location.INTERIOR) return true
|
||||
return false
|
||||
}
|
||||
isLineLocationUnknown(index) {
|
||||
if (index === 0)
|
||||
return this._aLocLine === OverlayLabel.LOC_UNKNOWN
|
||||
else
|
||||
return this._bLocLine === OverlayLabel.LOC_UNKNOWN
|
||||
|
||||
}
|
||||
isLine() {
|
||||
if (arguments.length === 0) {
|
||||
return this._aDim === OverlayLabel.DIM_LINE || this._bDim === OverlayLabel.DIM_LINE
|
||||
} else if (arguments.length === 1) {
|
||||
const index = arguments[0]
|
||||
if (index === 0)
|
||||
return this._aDim === OverlayLabel.DIM_LINE
|
||||
|
||||
return this._bDim === OverlayLabel.DIM_LINE
|
||||
}
|
||||
}
|
||||
isHole(index) {
|
||||
if (index === 0)
|
||||
return this._aIsHole
|
||||
else
|
||||
return this._bIsHole
|
||||
|
||||
}
|
||||
hasSides(index) {
|
||||
if (index === 0)
|
||||
return this._aLocLeft !== OverlayLabel.LOC_UNKNOWN || this._aLocRight !== OverlayLabel.LOC_UNKNOWN
|
||||
|
||||
return this._bLocLeft !== OverlayLabel.LOC_UNKNOWN || this._bLocRight !== OverlayLabel.LOC_UNKNOWN
|
||||
}
|
||||
isLineInterior(index) {
|
||||
if (index === 0)
|
||||
return this._aLocLine === Location.INTERIOR
|
||||
|
||||
return this._bLocLine === Location.INTERIOR
|
||||
}
|
||||
setLocationAll(index, loc) {
|
||||
if (index === 0) {
|
||||
this._aLocLine = loc
|
||||
this._aLocLeft = loc
|
||||
this._aLocRight = loc
|
||||
} else {
|
||||
this._bLocLine = loc
|
||||
this._bLocLeft = loc
|
||||
this._bLocRight = loc
|
||||
}
|
||||
}
|
||||
isCollapse(index) {
|
||||
return this.dimension(index) === OverlayLabel.DIM_COLLAPSE
|
||||
}
|
||||
setLocationCollapse(index) {
|
||||
const loc = this.isHole(index) ? Location.INTERIOR : Location.EXTERIOR
|
||||
if (index === 0)
|
||||
this._aLocLine = loc
|
||||
else
|
||||
this._bLocLine = loc
|
||||
|
||||
}
|
||||
setLocationLine(index, loc) {
|
||||
if (index === 0)
|
||||
this._aLocLine = loc
|
||||
else
|
||||
this._bLocLine = loc
|
||||
|
||||
}
|
||||
isBoundaryTouch() {
|
||||
return this.isBoundaryBoth() && this.getLocation(0, Position.RIGHT, true) !== this.getLocation(1, Position.RIGHT, true)
|
||||
}
|
||||
isBoundaryBoth() {
|
||||
return this._aDim === OverlayLabel.DIM_BOUNDARY && this._bDim === OverlayLabel.DIM_BOUNDARY
|
||||
}
|
||||
getLocation() {
|
||||
if (arguments.length === 1) {
|
||||
const index = arguments[0]
|
||||
if (index === 0)
|
||||
return this._aLocLine
|
||||
|
||||
return this._bLocLine
|
||||
} else if (arguments.length === 3) {
|
||||
const index = arguments[0], position = arguments[1], isForward = arguments[2]
|
||||
if (index === 0)
|
||||
switch (position) {
|
||||
case Position.LEFT:
|
||||
return isForward ? this._aLocLeft : this._aLocRight
|
||||
case Position.RIGHT:
|
||||
return isForward ? this._aLocRight : this._aLocLeft
|
||||
case Position.ON:
|
||||
return this._aLocLine
|
||||
}
|
||||
|
||||
switch (position) {
|
||||
case Position.LEFT:
|
||||
return isForward ? this._bLocLeft : this._bLocRight
|
||||
case Position.RIGHT:
|
||||
return isForward ? this._bLocRight : this._bLocLeft
|
||||
case Position.ON:
|
||||
return this._bLocLine
|
||||
}
|
||||
return OverlayLabel.LOC_UNKNOWN
|
||||
}
|
||||
}
|
||||
copy() {
|
||||
return new OverlayLabel(this)
|
||||
}
|
||||
toString() {
|
||||
if (arguments.length === 0) {
|
||||
return this.toString(true)
|
||||
} else if (arguments.length === 1) {
|
||||
const isForward = arguments[0]
|
||||
const buf = new StringBuilder()
|
||||
buf.append('A:')
|
||||
buf.append(this.locationString(0, isForward))
|
||||
buf.append('/B:')
|
||||
buf.append(this.locationString(1, isForward))
|
||||
return buf.toString()
|
||||
}
|
||||
}
|
||||
initLine(index) {
|
||||
if (index === 0) {
|
||||
this._aDim = OverlayLabel.DIM_LINE
|
||||
this._aLocLine = OverlayLabel.LOC_UNKNOWN
|
||||
} else {
|
||||
this._bDim = OverlayLabel.DIM_LINE
|
||||
this._bLocLine = OverlayLabel.LOC_UNKNOWN
|
||||
}
|
||||
}
|
||||
getLineLocation(index) {
|
||||
if (index === 0)
|
||||
return this._aLocLine
|
||||
else
|
||||
return this._bLocLine
|
||||
|
||||
}
|
||||
isCollapseAndNotPartInterior() {
|
||||
if (this._aDim === OverlayLabel.DIM_COLLAPSE && this._bDim === OverlayLabel.DIM_NOT_PART && this._bLocLine === Location.INTERIOR) return true
|
||||
if (this._bDim === OverlayLabel.DIM_COLLAPSE && this._aDim === OverlayLabel.DIM_NOT_PART && this._aLocLine === Location.INTERIOR) return true
|
||||
return false
|
||||
}
|
||||
initBoundary(index, locLeft, locRight, isHole) {
|
||||
if (index === 0) {
|
||||
this._aDim = OverlayLabel.DIM_BOUNDARY
|
||||
this._aIsHole = isHole
|
||||
this._aLocLeft = locLeft
|
||||
this._aLocRight = locRight
|
||||
this._aLocLine = Location.INTERIOR
|
||||
} else {
|
||||
this._bDim = OverlayLabel.DIM_BOUNDARY
|
||||
this._bIsHole = isHole
|
||||
this._bLocLeft = locLeft
|
||||
this._bLocRight = locRight
|
||||
this._bLocLine = Location.INTERIOR
|
||||
}
|
||||
}
|
||||
dimension(index) {
|
||||
if (index === 0) return this._aDim
|
||||
return this._bDim
|
||||
}
|
||||
isBoundarySingleton() {
|
||||
if (this._aDim === OverlayLabel.DIM_BOUNDARY && this._bDim === OverlayLabel.DIM_NOT_PART) return true
|
||||
if (this._bDim === OverlayLabel.DIM_BOUNDARY && this._aDim === OverlayLabel.DIM_NOT_PART) return true
|
||||
return false
|
||||
}
|
||||
isBoundaryCollapse() {
|
||||
if (this.isLine()) return false
|
||||
return !this.isBoundaryBoth()
|
||||
}
|
||||
isLinear(index) {
|
||||
if (index === 0)
|
||||
return this._aDim === OverlayLabel.DIM_LINE || this._aDim === OverlayLabel.DIM_COLLAPSE
|
||||
|
||||
return this._bDim === OverlayLabel.DIM_LINE || this._bDim === OverlayLabel.DIM_COLLAPSE
|
||||
}
|
||||
initNotPart(index) {
|
||||
if (index === 0)
|
||||
this._aDim = OverlayLabel.DIM_NOT_PART
|
||||
else
|
||||
this._bDim = OverlayLabel.DIM_NOT_PART
|
||||
|
||||
}
|
||||
locationString(index, isForward) {
|
||||
const buf = new StringBuilder()
|
||||
if (this.isBoundary(index)) {
|
||||
buf.append(Location.toLocationSymbol(this.getLocation(index, Position.LEFT, isForward)))
|
||||
buf.append(Location.toLocationSymbol(this.getLocation(index, Position.RIGHT, isForward)))
|
||||
} else {
|
||||
buf.append(Location.toLocationSymbol(index === 0 ? this._aLocLine : this._bLocLine))
|
||||
}
|
||||
if (this.isKnown(index)) buf.append(OverlayLabel.dimensionSymbol(index === 0 ? this._aDim : this._bDim))
|
||||
if (this.isCollapse(index))
|
||||
buf.append(OverlayLabel.ringRoleSymbol(index === 0 ? this._aIsHole : this._bIsHole))
|
||||
|
||||
return buf.toString()
|
||||
}
|
||||
}
|
||||
OverlayLabel.SYM_UNKNOWN = '#'
|
||||
OverlayLabel.SYM_BOUNDARY = 'B'
|
||||
OverlayLabel.SYM_COLLAPSE = 'C'
|
||||
OverlayLabel.SYM_LINE = 'L'
|
||||
OverlayLabel.DIM_UNKNOWN = -1
|
||||
OverlayLabel.DIM_NOT_PART = OverlayLabel.DIM_UNKNOWN
|
||||
OverlayLabel.DIM_LINE = 1
|
||||
OverlayLabel.DIM_BOUNDARY = 2
|
||||
OverlayLabel.DIM_COLLAPSE = 3
|
||||
OverlayLabel.LOC_UNKNOWN = Location.NONE
|
||||
@ -1,201 +0,0 @@
|
||||
import Location from '../../geom/Location.js'
|
||||
import ArrayDeque from '../../../../../java/util/ArrayDeque.js'
|
||||
import WKTWriter from '../../io/WKTWriter.js'
|
||||
import Position from '../../geom/Position.js'
|
||||
import OverlayNG from './OverlayNG.js'
|
||||
import TopologyException from '../../geom/TopologyException.js'
|
||||
import OverlayEdge from './OverlayEdge.js'
|
||||
import ArrayList from '../../../../../java/util/ArrayList.js'
|
||||
import Assert from '../../util/Assert.js'
|
||||
import StringBuilder from '../../../../../java/lang/StringBuilder.js'
|
||||
export default class OverlayLabeller {
|
||||
constructor() {
|
||||
OverlayLabeller.constructor_.apply(this, arguments)
|
||||
}
|
||||
static constructor_() {
|
||||
this._graph = null
|
||||
this._inputGeometry = null
|
||||
this._edges = null
|
||||
const graph = arguments[0], inputGeometry = arguments[1]
|
||||
this._graph = graph
|
||||
this._inputGeometry = inputGeometry
|
||||
this._edges = graph.getEdges()
|
||||
}
|
||||
static findPropagationStartEdge(nodeEdge, geomIndex) {
|
||||
let eStart = nodeEdge
|
||||
do {
|
||||
const label = eStart.getLabel()
|
||||
if (label.isBoundary(geomIndex)) {
|
||||
Assert.isTrue(label.hasSides(geomIndex))
|
||||
return eStart
|
||||
}
|
||||
eStart = eStart.oNext()
|
||||
} while (eStart !== nodeEdge)
|
||||
return null
|
||||
}
|
||||
static propagateLinearLocationAtNode(eNode, geomIndex, isInputLine, edgeStack) {
|
||||
const lineLoc = eNode.getLabel().getLineLocation(geomIndex)
|
||||
if (isInputLine && lineLoc !== Location.EXTERIOR) return null
|
||||
let e = eNode.oNextOE()
|
||||
do {
|
||||
const label = e.getLabel()
|
||||
if (label.isLineLocationUnknown(geomIndex)) {
|
||||
label.setLocationLine(geomIndex, lineLoc)
|
||||
edgeStack.addFirst(e.symOE())
|
||||
}
|
||||
e = e.oNextOE()
|
||||
} while (e !== eNode)
|
||||
}
|
||||
static findLinearEdgesWithLocation(edges, geomIndex) {
|
||||
const linearEdges = new ArrayList()
|
||||
for (const edge of edges) {
|
||||
const lbl = edge.getLabel()
|
||||
if (lbl.isLinear(geomIndex) && !lbl.isLineLocationUnknown(geomIndex))
|
||||
linearEdges.add(edge)
|
||||
|
||||
}
|
||||
return linearEdges
|
||||
}
|
||||
static toString() {
|
||||
if (arguments.length === 1 && arguments[0] instanceof OverlayEdge) {
|
||||
const nodeEdge = arguments[0]
|
||||
const orig = nodeEdge.orig()
|
||||
const sb = new StringBuilder()
|
||||
sb.append('Node( ' + WKTWriter.format(orig) + ' )' + '\n')
|
||||
let e = nodeEdge
|
||||
do {
|
||||
sb.append(' -> ' + e)
|
||||
if (e.isResultLinked()) {
|
||||
sb.append(' Link: ')
|
||||
sb.append(e.nextResult())
|
||||
}
|
||||
sb.append('\n')
|
||||
e = e.oNextOE()
|
||||
} while (e !== nodeEdge)
|
||||
return sb.toString()
|
||||
}
|
||||
}
|
||||
markInResultArea(e, overlayOpCode) {
|
||||
const label = e.getLabel()
|
||||
if (label.isBoundaryEither() && OverlayNG.isResultOfOp(overlayOpCode, label.getLocationBoundaryOrLine(0, Position.RIGHT, e.isForward()), label.getLocationBoundaryOrLine(1, Position.RIGHT, e.isForward())))
|
||||
e.markInResultArea()
|
||||
|
||||
}
|
||||
locateEdgeBothEnds(geomIndex, edge) {
|
||||
const locOrig = this._inputGeometry.locatePointInArea(geomIndex, edge.orig())
|
||||
const locDest = this._inputGeometry.locatePointInArea(geomIndex, edge.dest())
|
||||
const isInt = locOrig !== Location.EXTERIOR && locDest !== Location.EXTERIOR
|
||||
const edgeLoc = isInt ? Location.INTERIOR : Location.EXTERIOR
|
||||
return edgeLoc
|
||||
}
|
||||
labelAreaNodeEdges(nodes) {
|
||||
for (const nodeEdge of nodes) {
|
||||
this.propagateAreaLocations(nodeEdge, 0)
|
||||
if (this._inputGeometry.hasEdges(1))
|
||||
this.propagateAreaLocations(nodeEdge, 1)
|
||||
|
||||
}
|
||||
}
|
||||
labelCollapsedEdges() {
|
||||
for (const edge of this._edges) {
|
||||
if (edge.getLabel().isLineLocationUnknown(0))
|
||||
this.labelCollapsedEdge(edge, 0)
|
||||
|
||||
if (edge.getLabel().isLineLocationUnknown(1))
|
||||
this.labelCollapsedEdge(edge, 1)
|
||||
|
||||
}
|
||||
}
|
||||
labelCollapsedEdge(edge, geomIndex) {
|
||||
const label = edge.getLabel()
|
||||
if (!label.isCollapse(geomIndex)) return null
|
||||
label.setLocationCollapse(geomIndex)
|
||||
}
|
||||
propagateLinearLocations(geomIndex) {
|
||||
const linearEdges = OverlayLabeller.findLinearEdgesWithLocation(this._edges, geomIndex)
|
||||
if (linearEdges.size() <= 0) return null
|
||||
const edgeStack = new ArrayDeque(linearEdges)
|
||||
const isInputLine = this._inputGeometry.isLine(geomIndex)
|
||||
while (!edgeStack.isEmpty()) {
|
||||
const lineEdge = edgeStack.removeFirst()
|
||||
OverlayLabeller.propagateLinearLocationAtNode(lineEdge, geomIndex, isInputLine, edgeStack)
|
||||
}
|
||||
}
|
||||
propagateAreaLocations(nodeEdge, geomIndex) {
|
||||
if (!this._inputGeometry.isArea(geomIndex)) return null
|
||||
if (nodeEdge.degree() === 1) return null
|
||||
const eStart = OverlayLabeller.findPropagationStartEdge(nodeEdge, geomIndex)
|
||||
if (eStart === null) return null
|
||||
let currLoc = eStart.getLocation(geomIndex, Position.LEFT)
|
||||
let e = eStart.oNextOE()
|
||||
do {
|
||||
const label = e.getLabel()
|
||||
if (!label.isBoundary(geomIndex)) {
|
||||
label.setLocationLine(geomIndex, currLoc)
|
||||
} else {
|
||||
Assert.isTrue(label.hasSides(geomIndex))
|
||||
const locRight = e.getLocation(geomIndex, Position.RIGHT)
|
||||
if (locRight !== currLoc)
|
||||
throw new TopologyException('side location conflict: arg ' + geomIndex, e.getCoordinate())
|
||||
|
||||
const locLeft = e.getLocation(geomIndex, Position.LEFT)
|
||||
if (locLeft === Location.NONE)
|
||||
Assert.shouldNeverReachHere('found single null side at ' + e)
|
||||
|
||||
currLoc = locLeft
|
||||
}
|
||||
e = e.oNextOE()
|
||||
} while (e !== eStart)
|
||||
}
|
||||
labelDisconnectedEdges() {
|
||||
for (const edge of this._edges) {
|
||||
if (edge.getLabel().isLineLocationUnknown(0))
|
||||
this.labelDisconnectedEdge(edge, 0)
|
||||
|
||||
if (edge.getLabel().isLineLocationUnknown(1))
|
||||
this.labelDisconnectedEdge(edge, 1)
|
||||
|
||||
}
|
||||
}
|
||||
unmarkDuplicateEdgesFromResultArea() {
|
||||
for (const edge of this._edges)
|
||||
if (edge.isInResultAreaBoth())
|
||||
edge.unmarkFromResultAreaBoth()
|
||||
|
||||
|
||||
}
|
||||
locateEdge(geomIndex, edge) {
|
||||
const loc = this._inputGeometry.locatePointInArea(geomIndex, edge.orig())
|
||||
const edgeLoc = loc !== Location.EXTERIOR ? Location.INTERIOR : Location.EXTERIOR
|
||||
return edgeLoc
|
||||
}
|
||||
labelConnectedLinearEdges() {
|
||||
this.propagateLinearLocations(0)
|
||||
if (this._inputGeometry.hasEdges(1))
|
||||
this.propagateLinearLocations(1)
|
||||
|
||||
}
|
||||
labelDisconnectedEdge(edge, geomIndex) {
|
||||
const label = edge.getLabel()
|
||||
if (!this._inputGeometry.isArea(geomIndex)) {
|
||||
label.setLocationAll(geomIndex, Location.EXTERIOR)
|
||||
return null
|
||||
}
|
||||
|
||||
const edgeLoc = this.locateEdgeBothEnds(geomIndex, edge)
|
||||
label.setLocationAll(geomIndex, edgeLoc)
|
||||
}
|
||||
markResultAreaEdges(overlayOpCode) {
|
||||
for (const edge of this._edges)
|
||||
this.markInResultArea(edge, overlayOpCode)
|
||||
|
||||
}
|
||||
computeLabelling() {
|
||||
const nodes = this._graph.getNodeEdges()
|
||||
this.labelAreaNodeEdges(nodes)
|
||||
this.labelConnectedLinearEdges()
|
||||
this.labelCollapsedEdges()
|
||||
this.labelConnectedLinearEdges()
|
||||
this.labelDisconnectedEdges()
|
||||
}
|
||||
}
|
||||
@ -1,168 +0,0 @@
|
||||
import IndexedPointOnLineLocator from './IndexedPointOnLineLocator.js'
|
||||
import Location from '../../geom/Location.js'
|
||||
import CoordinateList from '../../geom/CoordinateList.js'
|
||||
import HashSet from '../../../../../java/util/HashSet.js'
|
||||
import CoordinateFilter from '../../geom/CoordinateFilter.js'
|
||||
import GeometryFactory from '../../geom/GeometryFactory.js'
|
||||
import OverlayNG from './OverlayNG.js'
|
||||
import OverlayUtil from './OverlayUtil.js'
|
||||
import ArrayList from '../../../../../java/util/ArrayList.js'
|
||||
import IndexedPointInAreaLocator from '../../algorithm/locate/IndexedPointInAreaLocator.js'
|
||||
import Assert from '../../util/Assert.js'
|
||||
export default class OverlayMixedPoints {
|
||||
constructor() {
|
||||
OverlayMixedPoints.constructor_.apply(this, arguments)
|
||||
}
|
||||
static constructor_() {
|
||||
this._opCode = null
|
||||
this._pm = null
|
||||
this._geomPoint = null
|
||||
this._geomNonPointInput = null
|
||||
this._geometryFactory = null
|
||||
this._isPointRHS = null
|
||||
this._geomNonPoint = null
|
||||
this._geomNonPointDim = null
|
||||
this._locator = null
|
||||
this._resultDim = null
|
||||
const opCode = arguments[0], geom0 = arguments[1], geom1 = arguments[2], pm = arguments[3]
|
||||
this._opCode = opCode
|
||||
this._pm = pm
|
||||
this._geometryFactory = geom0.getFactory()
|
||||
this._resultDim = OverlayUtil.resultDimension(opCode, geom0.getDimension(), geom1.getDimension())
|
||||
if (geom0.getDimension() === 0) {
|
||||
this._geomPoint = geom0
|
||||
this._geomNonPointInput = geom1
|
||||
this._isPointRHS = false
|
||||
} else {
|
||||
this._geomPoint = geom1
|
||||
this._geomNonPointInput = geom0
|
||||
this._isPointRHS = true
|
||||
}
|
||||
}
|
||||
static overlay(opCode, geom0, geom1, pm) {
|
||||
const overlay = new OverlayMixedPoints(opCode, geom0, geom1, pm)
|
||||
return overlay.getResult()
|
||||
}
|
||||
static extractCoordinates(points, pm) {
|
||||
const coords = new CoordinateList()
|
||||
points.apply(new (class {
|
||||
get interfaces_() {
|
||||
return [CoordinateFilter]
|
||||
}
|
||||
filter(coord) {
|
||||
const p = OverlayUtil.round(coord, pm)
|
||||
coords.add(p, false)
|
||||
}
|
||||
})())
|
||||
return coords.toCoordinateArray()
|
||||
}
|
||||
static extractPolygons(geom) {
|
||||
const list = new ArrayList()
|
||||
for (let i = 0; i < geom.getNumGeometries(); i++) {
|
||||
const poly = geom.getGeometryN(i)
|
||||
if (!poly.isEmpty())
|
||||
list.add(poly)
|
||||
|
||||
}
|
||||
return list
|
||||
}
|
||||
static extractLines(geom) {
|
||||
const list = new ArrayList()
|
||||
for (let i = 0; i < geom.getNumGeometries(); i++) {
|
||||
const line = geom.getGeometryN(i)
|
||||
if (!line.isEmpty())
|
||||
list.add(line)
|
||||
|
||||
}
|
||||
return list
|
||||
}
|
||||
createLocator(geomNonPoint) {
|
||||
if (this._geomNonPointDim === 2)
|
||||
return new IndexedPointInAreaLocator(geomNonPoint)
|
||||
else
|
||||
return new IndexedPointOnLineLocator(geomNonPoint)
|
||||
|
||||
}
|
||||
hasLocation(isCovered, coord) {
|
||||
const isExterior = Location.EXTERIOR === this._locator.locate(coord)
|
||||
if (isCovered)
|
||||
return !isExterior
|
||||
|
||||
return isExterior
|
||||
}
|
||||
computeIntersection(coords) {
|
||||
return this.createPointResult(this.findPoints(true, coords))
|
||||
}
|
||||
computeDifference(coords) {
|
||||
if (this._isPointRHS)
|
||||
return this.copyNonPoint()
|
||||
|
||||
return this.createPointResult(this.findPoints(false, coords))
|
||||
}
|
||||
computeUnion(coords) {
|
||||
const resultPointList = this.findPoints(false, coords)
|
||||
let resultLineList = null
|
||||
if (this._geomNonPointDim === 1)
|
||||
resultLineList = OverlayMixedPoints.extractLines(this._geomNonPoint)
|
||||
|
||||
let resultPolyList = null
|
||||
if (this._geomNonPointDim === 2)
|
||||
resultPolyList = OverlayMixedPoints.extractPolygons(this._geomNonPoint)
|
||||
|
||||
return OverlayUtil.createResultGeometry(resultPolyList, resultLineList, resultPointList, this._geometryFactory)
|
||||
}
|
||||
getResult() {
|
||||
this._geomNonPoint = this.prepareNonPoint(this._geomNonPointInput)
|
||||
this._geomNonPointDim = this._geomNonPoint.getDimension()
|
||||
this._locator = this.createLocator(this._geomNonPoint)
|
||||
const coords = OverlayMixedPoints.extractCoordinates(this._geomPoint, this._pm)
|
||||
switch (this._opCode) {
|
||||
case OverlayNG.INTERSECTION:
|
||||
return this.computeIntersection(coords)
|
||||
case OverlayNG.UNION:
|
||||
case OverlayNG.SYMDIFFERENCE:
|
||||
return this.computeUnion(coords)
|
||||
case OverlayNG.DIFFERENCE:
|
||||
return this.computeDifference(coords)
|
||||
}
|
||||
Assert.shouldNeverReachHere('Unknown overlay op code')
|
||||
return null
|
||||
}
|
||||
copyNonPoint() {
|
||||
if (this._geomNonPointInput !== this._geomNonPoint) return this._geomNonPoint
|
||||
return this._geomNonPoint.copy()
|
||||
}
|
||||
prepareNonPoint(geomInput) {
|
||||
if (this._resultDim === 0)
|
||||
return geomInput
|
||||
|
||||
const geomPrep = OverlayNG.union(this._geomNonPointInput, this._pm)
|
||||
return geomPrep
|
||||
}
|
||||
createPointResult(points) {
|
||||
if (points.size() === 0)
|
||||
return this._geometryFactory.createEmpty(0)
|
||||
else if (points.size() === 1)
|
||||
return points.get(0)
|
||||
|
||||
const pointsArray = GeometryFactory.toPointArray(points)
|
||||
return this._geometryFactory.createMultiPoint(pointsArray)
|
||||
}
|
||||
findPoints(isCovered, coords) {
|
||||
const resultCoords = new HashSet()
|
||||
for (const coord of coords)
|
||||
if (this.hasLocation(isCovered, coord))
|
||||
resultCoords.add(coord.copy())
|
||||
|
||||
|
||||
return this.createPoints(resultCoords)
|
||||
}
|
||||
createPoints(coords) {
|
||||
const points = new ArrayList()
|
||||
for (const coord of coords) {
|
||||
const point = this._geometryFactory.createPoint(coord)
|
||||
points.add(point)
|
||||
}
|
||||
return points
|
||||
}
|
||||
}
|
||||
@ -1,227 +0,0 @@
|
||||
import IntersectionPointBuilder from './IntersectionPointBuilder.js'
|
||||
import OverlayMixedPoints from './OverlayMixedPoints.js'
|
||||
import Location from '../../geom/Location.js'
|
||||
import OverlayPoints from './OverlayPoints.js'
|
||||
import Geometry from '../../geom/Geometry.js'
|
||||
import PolygonBuilder from './PolygonBuilder.js'
|
||||
import hasInterface from '../../../../../hasInterface.js'
|
||||
import Noder from '../../noding/Noder.js'
|
||||
import OverlayLabeller from './OverlayLabeller.js'
|
||||
import LineBuilder from './LineBuilder.js'
|
||||
import TopologyException from '../../geom/TopologyException.js'
|
||||
import ElevationModel from './ElevationModel.js'
|
||||
import EdgeNodingBuilder from './EdgeNodingBuilder.js'
|
||||
import OverlayUtil from './OverlayUtil.js'
|
||||
import PrecisionModel from '../../geom/PrecisionModel.js'
|
||||
import OverlayGraph from './OverlayGraph.js'
|
||||
import OverlayOp from '../overlay/OverlayOp.js'
|
||||
import InputGeometry from './InputGeometry.js'
|
||||
export default class OverlayNG {
|
||||
constructor() {
|
||||
OverlayNG.constructor_.apply(this, arguments)
|
||||
}
|
||||
static constructor_() {
|
||||
this._opCode = null
|
||||
this._inputGeom = null
|
||||
this._geomFact = null
|
||||
this._pm = null
|
||||
this._noder = null
|
||||
this._isStrictMode = OverlayNG.STRICT_MODE_DEFAULT
|
||||
this._isOptimized = true
|
||||
this._isAreaResultOnly = false
|
||||
this._isOutputEdges = false
|
||||
this._isOutputResultEdges = false
|
||||
this._isOutputNodedEdges = false
|
||||
if (arguments.length === 2) {
|
||||
const geom = arguments[0], pm = arguments[1]
|
||||
OverlayNG.constructor_.call(this, geom, null, pm, OverlayNG.UNION)
|
||||
} else if (arguments.length === 3) {
|
||||
const geom0 = arguments[0], geom1 = arguments[1], opCode = arguments[2]
|
||||
OverlayNG.constructor_.call(this, geom0, geom1, geom0.getFactory().getPrecisionModel(), opCode)
|
||||
} else if (arguments.length === 4) {
|
||||
const geom0 = arguments[0], geom1 = arguments[1], pm = arguments[2], opCode = arguments[3]
|
||||
this._pm = pm
|
||||
this._opCode = opCode
|
||||
this._geomFact = geom0.getFactory()
|
||||
this._inputGeom = new InputGeometry(geom0, geom1)
|
||||
}
|
||||
}
|
||||
static union() {
|
||||
if (arguments.length === 2) {
|
||||
const geom = arguments[0], pm = arguments[1]
|
||||
const ov = new OverlayNG(geom, pm)
|
||||
const geomOv = ov.getResult()
|
||||
return geomOv
|
||||
} else if (arguments.length === 3) {
|
||||
const geom = arguments[0], pm = arguments[1], noder = arguments[2]
|
||||
const ov = new OverlayNG(geom, pm)
|
||||
ov.setNoder(noder)
|
||||
ov.setStrictMode(true)
|
||||
const geomOv = ov.getResult()
|
||||
return geomOv
|
||||
}
|
||||
}
|
||||
static overlay() {
|
||||
if (arguments.length === 3) {
|
||||
const geom0 = arguments[0], geom1 = arguments[1], opCode = arguments[2]
|
||||
const ov = new OverlayNG(geom0, geom1, opCode)
|
||||
return ov.getResult()
|
||||
} else if (arguments.length === 4) {
|
||||
if (hasInterface(arguments[3], Noder) && (Number.isInteger(arguments[2]) && (arguments[0] instanceof Geometry && arguments[1] instanceof Geometry))) {
|
||||
const geom0 = arguments[0], geom1 = arguments[1], opCode = arguments[2], noder = arguments[3]
|
||||
const ov = new OverlayNG(geom0, geom1, null, opCode)
|
||||
ov.setNoder(noder)
|
||||
const geomOv = ov.getResult()
|
||||
return geomOv
|
||||
} else if (arguments[3] instanceof PrecisionModel && (Number.isInteger(arguments[2]) && (arguments[0] instanceof Geometry && arguments[1] instanceof Geometry))) {
|
||||
const geom0 = arguments[0], geom1 = arguments[1], opCode = arguments[2], pm = arguments[3]
|
||||
const ov = new OverlayNG(geom0, geom1, pm, opCode)
|
||||
const geomOv = ov.getResult()
|
||||
return geomOv
|
||||
}
|
||||
} else if (arguments.length === 5) {
|
||||
const geom0 = arguments[0], geom1 = arguments[1], opCode = arguments[2], pm = arguments[3], noder = arguments[4]
|
||||
const ov = new OverlayNG(geom0, geom1, pm, opCode)
|
||||
ov.setNoder(noder)
|
||||
const geomOv = ov.getResult()
|
||||
return geomOv
|
||||
}
|
||||
}
|
||||
static isResultOfOpPoint(label, opCode) {
|
||||
const loc0 = label.getLocation(0)
|
||||
const loc1 = label.getLocation(1)
|
||||
return OverlayNG.isResultOfOp(opCode, loc0, loc1)
|
||||
}
|
||||
static isEmpty(list) {
|
||||
return list === null || list.size() === 0
|
||||
}
|
||||
static isResultOfOp(overlayOpCode, loc0, loc1) {
|
||||
if (loc0 === Location.BOUNDARY) loc0 = Location.INTERIOR
|
||||
if (loc1 === Location.BOUNDARY) loc1 = Location.INTERIOR
|
||||
switch (overlayOpCode) {
|
||||
case OverlayNG.INTERSECTION:
|
||||
return loc0 === Location.INTERIOR && loc1 === Location.INTERIOR
|
||||
case OverlayNG.UNION:
|
||||
return loc0 === Location.INTERIOR || loc1 === Location.INTERIOR
|
||||
case OverlayNG.DIFFERENCE:
|
||||
return loc0 === Location.INTERIOR && loc1 !== Location.INTERIOR
|
||||
case OverlayNG.SYMDIFFERENCE:
|
||||
return loc0 === Location.INTERIOR && loc1 !== Location.INTERIOR || loc0 !== Location.INTERIOR && loc1 === Location.INTERIOR
|
||||
}
|
||||
return false
|
||||
}
|
||||
setOutputResultEdges(isOutputResultEdges) {
|
||||
this._isOutputResultEdges = isOutputResultEdges
|
||||
}
|
||||
getResult() {
|
||||
if (OverlayUtil.isEmptyResult(this._opCode, this._inputGeom.getGeometry(0), this._inputGeom.getGeometry(1), this._pm))
|
||||
return this.createEmptyResult()
|
||||
|
||||
const elevModel = ElevationModel.create(this._inputGeom.getGeometry(0), this._inputGeom.getGeometry(1))
|
||||
let result = null
|
||||
if (this._inputGeom.isAllPoints())
|
||||
result = OverlayPoints.overlay(this._opCode, this._inputGeom.getGeometry(0), this._inputGeom.getGeometry(1), this._pm)
|
||||
else if (!this._inputGeom.isSingle() && this._inputGeom.hasPoints())
|
||||
result = OverlayMixedPoints.overlay(this._opCode, this._inputGeom.getGeometry(0), this._inputGeom.getGeometry(1), this._pm)
|
||||
else
|
||||
result = this.computeEdgeOverlay()
|
||||
|
||||
elevModel.populateZ(result)
|
||||
return result
|
||||
}
|
||||
nodeEdges() {
|
||||
const nodingBuilder = new EdgeNodingBuilder(this._pm, this._noder)
|
||||
if (this._isOptimized) {
|
||||
const clipEnv = OverlayUtil.clippingEnvelope(this._opCode, this._inputGeom, this._pm)
|
||||
if (clipEnv !== null) nodingBuilder.setClipEnvelope(clipEnv)
|
||||
}
|
||||
const mergedEdges = nodingBuilder.build(this._inputGeom.getGeometry(0), this._inputGeom.getGeometry(1))
|
||||
this._inputGeom.setCollapsed(0, !nodingBuilder.hasEdgesFor(0))
|
||||
this._inputGeom.setCollapsed(1, !nodingBuilder.hasEdgesFor(1))
|
||||
return mergedEdges
|
||||
}
|
||||
setOutputNodedEdges(isOutputNodedEdges) {
|
||||
this._isOutputEdges = true
|
||||
this._isOutputNodedEdges = isOutputNodedEdges
|
||||
}
|
||||
buildGraph(edges) {
|
||||
const graph = new OverlayGraph()
|
||||
for (const e of edges)
|
||||
graph.addEdge(e.getCoordinates(), e.createLabel())
|
||||
|
||||
return graph
|
||||
}
|
||||
setAreaResultOnly(isAreaResultOnly) {
|
||||
this._isAreaResultOnly = isAreaResultOnly
|
||||
}
|
||||
setOptimized(isOptimized) {
|
||||
this._isOptimized = isOptimized
|
||||
}
|
||||
extractResult(opCode, graph) {
|
||||
const isAllowMixedIntResult = !this._isStrictMode
|
||||
const resultAreaEdges = graph.getResultAreaEdges()
|
||||
const polyBuilder = new PolygonBuilder(resultAreaEdges, this._geomFact)
|
||||
const resultPolyList = polyBuilder.getPolygons()
|
||||
const hasResultAreaComponents = resultPolyList.size() > 0
|
||||
let resultLineList = null
|
||||
let resultPointList = null
|
||||
if (!this._isAreaResultOnly) {
|
||||
const allowResultLines = !hasResultAreaComponents || isAllowMixedIntResult || opCode === OverlayNG.SYMDIFFERENCE || opCode === OverlayNG.UNION
|
||||
if (allowResultLines) {
|
||||
const lineBuilder = new LineBuilder(this._inputGeom, graph, hasResultAreaComponents, opCode, this._geomFact)
|
||||
lineBuilder.setStrictMode(this._isStrictMode)
|
||||
resultLineList = lineBuilder.getLines()
|
||||
}
|
||||
const hasResultComponents = hasResultAreaComponents || resultLineList.size() > 0
|
||||
const allowResultPoints = !hasResultComponents || isAllowMixedIntResult
|
||||
if (opCode === OverlayNG.INTERSECTION && allowResultPoints) {
|
||||
const pointBuilder = new IntersectionPointBuilder(graph, this._geomFact)
|
||||
pointBuilder.setStrictMode(this._isStrictMode)
|
||||
resultPointList = pointBuilder.getPoints()
|
||||
}
|
||||
}
|
||||
if (OverlayNG.isEmpty(resultPolyList) && OverlayNG.isEmpty(resultLineList) && OverlayNG.isEmpty(resultPointList)) return this.createEmptyResult()
|
||||
const resultGeom = OverlayUtil.createResultGeometry(resultPolyList, resultLineList, resultPointList, this._geomFact)
|
||||
return resultGeom
|
||||
}
|
||||
computeEdgeOverlay() {
|
||||
const edges = this.nodeEdges()
|
||||
const graph = this.buildGraph(edges)
|
||||
if (this._isOutputNodedEdges)
|
||||
return OverlayUtil.toLines(graph, this._isOutputEdges, this._geomFact)
|
||||
|
||||
this.labelGraph(graph)
|
||||
if (this._isOutputEdges || this._isOutputResultEdges)
|
||||
return OverlayUtil.toLines(graph, this._isOutputEdges, this._geomFact)
|
||||
|
||||
const result = this.extractResult(this._opCode, graph)
|
||||
if (OverlayUtil.isFloating(this._pm)) {
|
||||
const isAreaConsistent = OverlayUtil.isResultAreaConsistent(this._inputGeom.getGeometry(0), this._inputGeom.getGeometry(1), this._opCode, result)
|
||||
if (!isAreaConsistent) throw new TopologyException('Result area inconsistent with overlay operation')
|
||||
}
|
||||
return result
|
||||
}
|
||||
setOutputEdges(isOutputEdges) {
|
||||
this._isOutputEdges = isOutputEdges
|
||||
}
|
||||
createEmptyResult() {
|
||||
return OverlayUtil.createEmptyResult(OverlayUtil.resultDimension(this._opCode, this._inputGeom.getDimension(0), this._inputGeom.getDimension(1)), this._geomFact)
|
||||
}
|
||||
setNoder(noder) {
|
||||
this._noder = noder
|
||||
}
|
||||
setStrictMode(isStrictMode) {
|
||||
this._isStrictMode = isStrictMode
|
||||
}
|
||||
labelGraph(graph) {
|
||||
const labeller = new OverlayLabeller(graph, this._inputGeom)
|
||||
labeller.computeLabelling()
|
||||
labeller.markResultAreaEdges(this._opCode)
|
||||
labeller.unmarkDuplicateEdgesFromResultArea()
|
||||
}
|
||||
}
|
||||
OverlayNG.INTERSECTION = OverlayOp.INTERSECTION
|
||||
OverlayNG.UNION = OverlayOp.UNION
|
||||
OverlayNG.DIFFERENCE = OverlayOp.DIFFERENCE
|
||||
OverlayNG.SYMDIFFERENCE = OverlayOp.SYMDIFFERENCE
|
||||
OverlayNG.STRICT_MODE_DEFAULT = false
|
||||
@ -1,142 +0,0 @@
|
||||
import Geometry from '../../geom/Geometry.js'
|
||||
import hasInterface from '../../../../../hasInterface.js'
|
||||
import UnionStrategy from '../union/UnionStrategy.js'
|
||||
import Collection from '../../../../../java/util/Collection.js'
|
||||
import OverlayNG from './OverlayNG.js'
|
||||
import UnaryUnionOp from '../union/UnaryUnionOp.js'
|
||||
import TopologyException from '../../geom/TopologyException.js'
|
||||
import SnappingNoder from '../../noding/snap/SnappingNoder.js'
|
||||
import PrecisionUtil from './PrecisionUtil.js'
|
||||
import PrecisionModel from '../../geom/PrecisionModel.js'
|
||||
import RuntimeException from '../../../../../java/lang/RuntimeException.js'
|
||||
export default class OverlayNGRobust {
|
||||
static union() {
|
||||
if (arguments.length === 1) {
|
||||
if (arguments[0] instanceof Geometry) {
|
||||
const geom = arguments[0]
|
||||
const op = new UnaryUnionOp(geom)
|
||||
op.setUnionFunction(OverlayNGRobust.OVERLAY_UNION)
|
||||
return op.union()
|
||||
} else if (hasInterface(arguments[0], Collection)) {
|
||||
const geoms = arguments[0]
|
||||
const op = new UnaryUnionOp(geoms)
|
||||
op.setUnionFunction(OverlayNGRobust.OVERLAY_UNION)
|
||||
return op.union()
|
||||
}
|
||||
} else if (arguments.length === 2) {
|
||||
const geoms = arguments[0], geomFact = arguments[1]
|
||||
const op = new UnaryUnionOp(geoms, geomFact)
|
||||
op.setUnionFunction(OverlayNGRobust.OVERLAY_UNION)
|
||||
return op.union()
|
||||
}
|
||||
}
|
||||
static overlay(geom0, geom1, opCode) {
|
||||
let result = null
|
||||
let exOriginal = null
|
||||
try {
|
||||
result = OverlayNG.overlay(geom0, geom1, opCode)
|
||||
return result
|
||||
} catch (ex) {
|
||||
if (ex instanceof RuntimeException)
|
||||
exOriginal = ex
|
||||
else throw ex
|
||||
} finally {}
|
||||
result = OverlayNGRobust.overlaySnapTries(geom0, geom1, opCode)
|
||||
if (result !== null) return result
|
||||
result = OverlayNGRobust.overlaySR(geom0, geom1, opCode)
|
||||
if (result !== null) return result
|
||||
throw exOriginal
|
||||
}
|
||||
static overlaySnapTol(geom0, geom1, opCode, snapTol) {
|
||||
const snapNoder = new SnappingNoder(snapTol)
|
||||
return OverlayNG.overlay(geom0, geom1, opCode, snapNoder)
|
||||
}
|
||||
static overlaySnapTries(geom0, geom1, opCode) {
|
||||
let result = null
|
||||
let snapTol = OverlayNGRobust.snapTolerance(geom0, geom1)
|
||||
for (let i = 0; i < OverlayNGRobust.NUM_SNAP_TRIES; i++) {
|
||||
result = OverlayNGRobust.overlaySnapping(geom0, geom1, opCode, snapTol)
|
||||
if (result !== null) return result
|
||||
result = OverlayNGRobust.overlaySnapBoth(geom0, geom1, opCode, snapTol)
|
||||
if (result !== null) return result
|
||||
snapTol = snapTol * 10
|
||||
}
|
||||
return null
|
||||
}
|
||||
static overlaySR(geom0, geom1, opCode) {
|
||||
let result = null
|
||||
try {
|
||||
const scaleSafe = PrecisionUtil.safeScale(geom0, geom1)
|
||||
const pmSafe = new PrecisionModel(scaleSafe)
|
||||
result = OverlayNG.overlay(geom0, geom1, opCode, pmSafe)
|
||||
return result
|
||||
} catch (ex) {
|
||||
if (ex instanceof TopologyException) {} else {
|
||||
throw ex
|
||||
}
|
||||
} finally {}
|
||||
return null
|
||||
}
|
||||
static snapSelf(geom, snapTol) {
|
||||
const ov = new OverlayNG(geom, null)
|
||||
const snapNoder = new SnappingNoder(snapTol)
|
||||
ov.setNoder(snapNoder)
|
||||
ov.setStrictMode(true)
|
||||
return ov.getResult()
|
||||
}
|
||||
static overlaySnapBoth(geom0, geom1, opCode, snapTol) {
|
||||
try {
|
||||
const snap0 = OverlayNGRobust.snapSelf(geom0, snapTol)
|
||||
const snap1 = OverlayNGRobust.snapSelf(geom1, snapTol)
|
||||
return OverlayNGRobust.overlaySnapTol(snap0, snap1, opCode, snapTol)
|
||||
} catch (ex) {
|
||||
if (ex instanceof TopologyException) {} else {
|
||||
throw ex
|
||||
}
|
||||
} finally {}
|
||||
return null
|
||||
}
|
||||
static overlaySnapping(geom0, geom1, opCode, snapTol) {
|
||||
try {
|
||||
return OverlayNGRobust.overlaySnapTol(geom0, geom1, opCode, snapTol)
|
||||
} catch (ex) {
|
||||
if (ex instanceof TopologyException) {} else {
|
||||
throw ex
|
||||
}
|
||||
} finally {}
|
||||
return null
|
||||
}
|
||||
static ordinateMagnitude(geom) {
|
||||
if (geom === null || geom.isEmpty()) return 0
|
||||
const env = geom.getEnvelopeInternal()
|
||||
const magMax = Math.max(Math.abs(env.getMaxX()), Math.abs(env.getMaxY()))
|
||||
const magMin = Math.max(Math.abs(env.getMinX()), Math.abs(env.getMinY()))
|
||||
return Math.max(magMax, magMin)
|
||||
}
|
||||
static snapTolerance() {
|
||||
if (arguments.length === 1) {
|
||||
const geom = arguments[0]
|
||||
const magnitude = OverlayNGRobust.ordinateMagnitude(geom)
|
||||
return magnitude / OverlayNGRobust.SNAP_TOL_FACTOR
|
||||
} else if (arguments.length === 2) {
|
||||
const geom0 = arguments[0], geom1 = arguments[1]
|
||||
const tol0 = OverlayNGRobust.snapTolerance(geom0)
|
||||
const tol1 = OverlayNGRobust.snapTolerance(geom1)
|
||||
const snapTol = Math.max(tol0, tol1)
|
||||
return snapTol
|
||||
}
|
||||
}
|
||||
}
|
||||
OverlayNGRobust.OVERLAY_UNION = new (class {
|
||||
get interfaces_() {
|
||||
return [UnionStrategy]
|
||||
}
|
||||
union(g0, g1) {
|
||||
return OverlayNGRobust.overlay(g0, g1, OverlayNG.UNION)
|
||||
}
|
||||
isFloatingPrecision() {
|
||||
return true
|
||||
}
|
||||
})()
|
||||
OverlayNGRobust.NUM_SNAP_TRIES = 5
|
||||
OverlayNGRobust.SNAP_TOL_FACTOR = 1e12
|
||||
@ -1,107 +0,0 @@
|
||||
import HashMap from '../../../../../java/util/HashMap.js'
|
||||
import Point from '../../geom/Point.js'
|
||||
import OverlayNG from './OverlayNG.js'
|
||||
import OverlayUtil from './OverlayUtil.js'
|
||||
import GeometryComponentFilter from '../../geom/GeometryComponentFilter.js'
|
||||
import CoordinateSequence from '../../geom/CoordinateSequence.js'
|
||||
import ArrayList from '../../../../../java/util/ArrayList.js'
|
||||
export default class OverlayPoints {
|
||||
constructor() {
|
||||
OverlayPoints.constructor_.apply(this, arguments)
|
||||
}
|
||||
static constructor_() {
|
||||
this._opCode = null
|
||||
this._geom0 = null
|
||||
this._geom1 = null
|
||||
this._pm = null
|
||||
this._geometryFactory = null
|
||||
this._resultList = null
|
||||
const opCode = arguments[0], geom0 = arguments[1], geom1 = arguments[2], pm = arguments[3]
|
||||
this._opCode = opCode
|
||||
this._geom0 = geom0
|
||||
this._geom1 = geom1
|
||||
this._pm = pm
|
||||
this._geometryFactory = geom0.getFactory()
|
||||
}
|
||||
static overlay(opCode, geom0, geom1, pm) {
|
||||
const overlay = new OverlayPoints(opCode, geom0, geom1, pm)
|
||||
return overlay.getResult()
|
||||
}
|
||||
static roundCoord(pt, pm) {
|
||||
const p = pt.getCoordinate()
|
||||
if (OverlayUtil.isFloating(pm)) return p
|
||||
const p2 = p.copy()
|
||||
pm.makePrecise(p2)
|
||||
return p2
|
||||
}
|
||||
buildPointMap(geoms) {
|
||||
const map = new HashMap()
|
||||
geoms.apply(new (class {
|
||||
get interfaces_() {
|
||||
return [GeometryComponentFilter]
|
||||
}
|
||||
filter(geom) {
|
||||
if (!(geom instanceof Point)) return null
|
||||
if (geom.isEmpty()) return null
|
||||
const pt = geom
|
||||
const p = OverlayPoints.roundCoord(pt, this._pm)
|
||||
if (!map.containsKey(p)) map.put(p, pt)
|
||||
}
|
||||
})())
|
||||
return map
|
||||
}
|
||||
computeIntersection(map0, map1, resultList) {
|
||||
for (const entry of map0.entrySet())
|
||||
if (map1.containsKey(entry.getKey()))
|
||||
resultList.add(this.copyPoint(entry.getValue()))
|
||||
|
||||
|
||||
}
|
||||
computeDifference(map0, map1, resultList) {
|
||||
for (const entry of map0.entrySet())
|
||||
if (!map1.containsKey(entry.getKey()))
|
||||
resultList.add(this.copyPoint(entry.getValue()))
|
||||
|
||||
|
||||
}
|
||||
computeUnion(map0, map1, resultList) {
|
||||
for (const p of map0.values())
|
||||
resultList.add(this.copyPoint(p))
|
||||
|
||||
for (const entry of map1.entrySet())
|
||||
if (!map0.containsKey(entry.getKey()))
|
||||
resultList.add(this.copyPoint(entry.getValue()))
|
||||
|
||||
|
||||
}
|
||||
getResult() {
|
||||
const map0 = this.buildPointMap(this._geom0)
|
||||
const map1 = this.buildPointMap(this._geom1)
|
||||
this._resultList = new ArrayList()
|
||||
switch (this._opCode) {
|
||||
case OverlayNG.INTERSECTION:
|
||||
this.computeIntersection(map0, map1, this._resultList)
|
||||
break
|
||||
case OverlayNG.UNION:
|
||||
this.computeUnion(map0, map1, this._resultList)
|
||||
break
|
||||
case OverlayNG.DIFFERENCE:
|
||||
this.computeDifference(map0, map1, this._resultList)
|
||||
break
|
||||
case OverlayNG.SYMDIFFERENCE:
|
||||
this.computeDifference(map0, map1, this._resultList)
|
||||
this.computeDifference(map1, map0, this._resultList)
|
||||
break
|
||||
}
|
||||
if (this._resultList.isEmpty()) return OverlayUtil.createEmptyResult(0, this._geometryFactory)
|
||||
return this._geometryFactory.buildGeometry(this._resultList)
|
||||
}
|
||||
copyPoint(pt) {
|
||||
if (OverlayUtil.isFloating(this._pm)) return pt.copy()
|
||||
const seq = pt.getCoordinateSequence()
|
||||
const seq2 = seq.copy()
|
||||
seq2.setOrdinate(0, CoordinateSequence.X, this._pm.makePrecise(seq.getX(0)))
|
||||
seq2.setOrdinate(0, CoordinateSequence.Y, this._pm.makePrecise(seq.getY(0)))
|
||||
return this._geometryFactory.createPoint(seq2)
|
||||
}
|
||||
}
|
||||
@ -1,192 +0,0 @@
|
||||
import Coordinate from '../../geom/Coordinate.js'
|
||||
import Point from '../../geom/Point.js'
|
||||
import OverlayNG from './OverlayNG.js'
|
||||
import RobustClipEnvelopeComputer from './RobustClipEnvelopeComputer.js'
|
||||
import PrecisionModel from '../../geom/PrecisionModel.js'
|
||||
import ArrayList from '../../../../../java/util/ArrayList.js'
|
||||
import Assert from '../../util/Assert.js'
|
||||
export default class OverlayUtil {
|
||||
static labelForResult(edge) {
|
||||
return edge.getLabel().toString(edge.isForward()) + (edge.isInResultArea() ? ' Res' : '')
|
||||
}
|
||||
static isGreater(v1, v2, tol) {
|
||||
return v1 >= v2 * (1 - tol)
|
||||
}
|
||||
static isEmptyResult(opCode, a, b, pm) {
|
||||
switch (opCode) {
|
||||
case OverlayNG.INTERSECTION:
|
||||
if (OverlayUtil.isEnvDisjoint(a, b, pm)) return true
|
||||
break
|
||||
case OverlayNG.DIFFERENCE:
|
||||
if (OverlayUtil.isEmpty(a)) return true
|
||||
break
|
||||
case OverlayNG.UNION:
|
||||
case OverlayNG.SYMDIFFERENCE:
|
||||
if (OverlayUtil.isEmpty(a) && OverlayUtil.isEmpty(b)) return true
|
||||
break
|
||||
}
|
||||
return false
|
||||
}
|
||||
static isEnvDisjoint(a, b, pm) {
|
||||
if (OverlayUtil.isEmpty(a) || OverlayUtil.isEmpty(b)) return true
|
||||
if (OverlayUtil.isFloating(pm))
|
||||
return a.getEnvelopeInternal().disjoint(b.getEnvelopeInternal())
|
||||
|
||||
return OverlayUtil.isDisjoint(a.getEnvelopeInternal(), b.getEnvelopeInternal(), pm)
|
||||
}
|
||||
static safeExpandDistance(env, pm) {
|
||||
let envExpandDist = null
|
||||
if (OverlayUtil.isFloating(pm)) {
|
||||
let minSize = Math.min(env.getHeight(), env.getWidth())
|
||||
if (minSize <= 0.0)
|
||||
minSize = Math.max(env.getHeight(), env.getWidth())
|
||||
|
||||
envExpandDist = OverlayUtil.SAFE_ENV_BUFFER_FACTOR * minSize
|
||||
} else {
|
||||
const gridSize = 1.0 / pm.getScale()
|
||||
envExpandDist = OverlayUtil.SAFE_ENV_GRID_FACTOR * gridSize
|
||||
}
|
||||
return envExpandDist
|
||||
}
|
||||
static createResultGeometry(resultPolyList, resultLineList, resultPointList, geometryFactory) {
|
||||
const geomList = new ArrayList()
|
||||
if (resultPolyList !== null) geomList.addAll(resultPolyList)
|
||||
if (resultLineList !== null) geomList.addAll(resultLineList)
|
||||
if (resultPointList !== null) geomList.addAll(resultPointList)
|
||||
return geometryFactory.buildGeometry(geomList)
|
||||
}
|
||||
static safeEnv(env, pm) {
|
||||
const envExpandDist = OverlayUtil.safeExpandDistance(env, pm)
|
||||
const safeEnv = env.copy()
|
||||
safeEnv.expandBy(envExpandDist)
|
||||
return safeEnv
|
||||
}
|
||||
static isFloating(pm) {
|
||||
if (pm === null) return true
|
||||
return pm.isFloating()
|
||||
}
|
||||
static isResultAreaConsistent(geom0, geom1, opCode, result) {
|
||||
if (geom0 === null || geom1 === null) return true
|
||||
const areaResult = result.getArea()
|
||||
const areaA = geom0.getArea()
|
||||
const areaB = geom1.getArea()
|
||||
let isConsistent = true
|
||||
switch (opCode) {
|
||||
case OverlayNG.INTERSECTION:
|
||||
isConsistent = OverlayUtil.isLess(areaResult, areaA, OverlayUtil.AREA_HEURISTIC_TOLERANCE) && OverlayUtil.isLess(areaResult, areaB, OverlayUtil.AREA_HEURISTIC_TOLERANCE)
|
||||
break
|
||||
case OverlayNG.DIFFERENCE:
|
||||
isConsistent = OverlayUtil.isLess(areaResult, areaA, OverlayUtil.AREA_HEURISTIC_TOLERANCE) && OverlayUtil.isGreater(areaResult, areaA - areaB, OverlayUtil.AREA_HEURISTIC_TOLERANCE)
|
||||
break
|
||||
case OverlayNG.SYMDIFFERENCE:
|
||||
isConsistent = OverlayUtil.isLess(areaResult, areaA + areaB, OverlayUtil.AREA_HEURISTIC_TOLERANCE)
|
||||
break
|
||||
case OverlayNG.UNION:
|
||||
isConsistent = OverlayUtil.isLess(areaA, areaResult, OverlayUtil.AREA_HEURISTIC_TOLERANCE) && OverlayUtil.isLess(areaB, areaResult, OverlayUtil.AREA_HEURISTIC_TOLERANCE) && OverlayUtil.isGreater(areaResult, areaA - areaB, OverlayUtil.AREA_HEURISTIC_TOLERANCE)
|
||||
break
|
||||
}
|
||||
return isConsistent
|
||||
}
|
||||
static clippingEnvelope(opCode, inputGeom, pm) {
|
||||
const resultEnv = OverlayUtil.resultEnvelope(opCode, inputGeom, pm)
|
||||
if (resultEnv === null) return null
|
||||
const clipEnv = RobustClipEnvelopeComputer.getEnvelope(inputGeom.getGeometry(0), inputGeom.getGeometry(1), resultEnv)
|
||||
const safeEnv = OverlayUtil.safeEnv(clipEnv, pm)
|
||||
return safeEnv
|
||||
}
|
||||
static round() {
|
||||
if (arguments[0] instanceof Point && arguments[1] instanceof PrecisionModel) {
|
||||
const pt = arguments[0], pm = arguments[1]
|
||||
if (pt.isEmpty()) return null
|
||||
return OverlayUtil.round(pt.getCoordinate(), pm)
|
||||
} else if (arguments[0] instanceof Coordinate && arguments[1] instanceof PrecisionModel) {
|
||||
const p = arguments[0], pm = arguments[1]
|
||||
if (!OverlayUtil.isFloating(pm)) {
|
||||
const pRound = p.copy()
|
||||
pm.makePrecise(pRound)
|
||||
return pRound
|
||||
}
|
||||
return p
|
||||
}
|
||||
}
|
||||
static resultDimension(opCode, dim0, dim1) {
|
||||
let resultDimension = -1
|
||||
switch (opCode) {
|
||||
case OverlayNG.INTERSECTION:
|
||||
resultDimension = Math.min(dim0, dim1)
|
||||
break
|
||||
case OverlayNG.UNION:
|
||||
resultDimension = Math.max(dim0, dim1)
|
||||
break
|
||||
case OverlayNG.DIFFERENCE:
|
||||
resultDimension = dim0
|
||||
break
|
||||
case OverlayNG.SYMDIFFERENCE:
|
||||
resultDimension = Math.max(dim0, dim1)
|
||||
break
|
||||
}
|
||||
return resultDimension
|
||||
}
|
||||
static toLines(graph, isOutputEdges, geomFact) {
|
||||
const lines = new ArrayList()
|
||||
for (const edge of graph.getEdges()) {
|
||||
const includeEdge = isOutputEdges || edge.isInResultArea()
|
||||
if (!includeEdge) continue
|
||||
const pts = edge.getCoordinatesOriented()
|
||||
const line = geomFact.createLineString(pts)
|
||||
line.setUserData(OverlayUtil.labelForResult(edge))
|
||||
lines.add(line)
|
||||
}
|
||||
return geomFact.buildGeometry(lines)
|
||||
}
|
||||
static createEmptyResult(dim, geomFact) {
|
||||
let result = null
|
||||
switch (dim) {
|
||||
case 0:
|
||||
result = geomFact.createPoint()
|
||||
break
|
||||
case 1:
|
||||
result = geomFact.createLineString()
|
||||
break
|
||||
case 2:
|
||||
result = geomFact.createPolygon()
|
||||
break
|
||||
case -1:
|
||||
result = geomFact.createGeometryCollection()
|
||||
break
|
||||
default:
|
||||
Assert.shouldNeverReachHere('Unable to determine overlay result geometry dimension')
|
||||
}
|
||||
return result
|
||||
}
|
||||
static resultEnvelope(opCode, inputGeom, pm) {
|
||||
let overlapEnv = null
|
||||
switch (opCode) {
|
||||
case OverlayNG.INTERSECTION:
|
||||
const envA = OverlayUtil.safeEnv(inputGeom.getEnvelope(0), pm)
|
||||
const envB = OverlayUtil.safeEnv(inputGeom.getEnvelope(1), pm)
|
||||
overlapEnv = envA.intersection(envB)
|
||||
break
|
||||
case OverlayNG.DIFFERENCE:
|
||||
overlapEnv = OverlayUtil.safeEnv(inputGeom.getEnvelope(0), pm)
|
||||
break
|
||||
}
|
||||
return overlapEnv
|
||||
}
|
||||
static isEmpty(geom) {
|
||||
return geom === null || geom.isEmpty()
|
||||
}
|
||||
static isLess(v1, v2, tol) {
|
||||
return v1 <= v2 * (1 + tol)
|
||||
}
|
||||
static isDisjoint(envA, envB, pm) {
|
||||
if (pm.makePrecise(envB.getMinX()) > pm.makePrecise(envA.getMaxX())) return true
|
||||
if (pm.makePrecise(envB.getMaxX()) < pm.makePrecise(envA.getMinX())) return true
|
||||
if (pm.makePrecise(envB.getMinY()) > pm.makePrecise(envA.getMaxY())) return true
|
||||
if (pm.makePrecise(envB.getMaxY()) < pm.makePrecise(envA.getMinY())) return true
|
||||
return false
|
||||
}
|
||||
}
|
||||
OverlayUtil.SAFE_ENV_BUFFER_FACTOR = 0.1
|
||||
OverlayUtil.SAFE_ENV_GRID_FACTOR = 3
|
||||
OverlayUtil.AREA_HEURISTIC_TOLERANCE = 0.1
|
||||
@ -1,106 +0,0 @@
|
||||
import TopologyException from '../../geom/TopologyException.js'
|
||||
import MaximalEdgeRing from './MaximalEdgeRing.js'
|
||||
import ArrayList from '../../../../../java/util/ArrayList.js'
|
||||
import Assert from '../../util/Assert.js'
|
||||
export default class PolygonBuilder {
|
||||
constructor() {
|
||||
PolygonBuilder.constructor_.apply(this, arguments)
|
||||
}
|
||||
static constructor_() {
|
||||
this._geometryFactory = null
|
||||
this._shellList = new ArrayList()
|
||||
this._freeHoleList = new ArrayList()
|
||||
this._isEnforcePolygonal = true
|
||||
if (arguments.length === 2) {
|
||||
const resultAreaEdges = arguments[0], geomFact = arguments[1]
|
||||
PolygonBuilder.constructor_.call(this, resultAreaEdges, geomFact, true)
|
||||
} else if (arguments.length === 3) {
|
||||
const resultAreaEdges = arguments[0], geomFact = arguments[1], isEnforcePolygonal = arguments[2]
|
||||
this._geometryFactory = geomFact
|
||||
this._isEnforcePolygonal = isEnforcePolygonal
|
||||
this.buildRings(resultAreaEdges)
|
||||
}
|
||||
}
|
||||
static buildMaximalRings(edges) {
|
||||
const edgeRings = new ArrayList()
|
||||
for (const e of edges)
|
||||
if (e.isInResultArea() && e.getLabel().isBoundaryEither())
|
||||
if (e.getEdgeRingMax() === null) {
|
||||
const er = new MaximalEdgeRing(e)
|
||||
edgeRings.add(er)
|
||||
}
|
||||
|
||||
|
||||
return edgeRings
|
||||
}
|
||||
static assignHoles(shell, edgeRings) {
|
||||
for (const er of edgeRings)
|
||||
if (er.isHole())
|
||||
er.setShell(shell)
|
||||
|
||||
|
||||
}
|
||||
buildRings(resultAreaEdges) {
|
||||
this.linkResultAreaEdgesMax(resultAreaEdges)
|
||||
const maxRings = PolygonBuilder.buildMaximalRings(resultAreaEdges)
|
||||
this.buildMinimalRings(maxRings)
|
||||
this.placeFreeHoles(this._shellList, this._freeHoleList)
|
||||
}
|
||||
assignShellsAndHoles(minRings) {
|
||||
const shell = this.findSingleShell(minRings)
|
||||
if (shell !== null) {
|
||||
PolygonBuilder.assignHoles(shell, minRings)
|
||||
this._shellList.add(shell)
|
||||
} else {
|
||||
this._freeHoleList.addAll(minRings)
|
||||
}
|
||||
}
|
||||
buildMinimalRings(maxRings) {
|
||||
for (const erMax of maxRings) {
|
||||
const minRings = erMax.buildMinimalRings(this._geometryFactory)
|
||||
this.assignShellsAndHoles(minRings)
|
||||
}
|
||||
}
|
||||
computePolygons(shellList) {
|
||||
const resultPolyList = new ArrayList()
|
||||
for (const er of shellList) {
|
||||
const poly = er.toPolygon(this._geometryFactory)
|
||||
resultPolyList.add(poly)
|
||||
}
|
||||
return resultPolyList
|
||||
}
|
||||
findSingleShell(edgeRings) {
|
||||
let shellCount = 0
|
||||
let shell = null
|
||||
for (const er of edgeRings)
|
||||
if (!er.isHole()) {
|
||||
shell = er
|
||||
shellCount++
|
||||
}
|
||||
|
||||
Assert.isTrue(shellCount <= 1, 'found two shells in EdgeRing list')
|
||||
return shell
|
||||
}
|
||||
placeFreeHoles(shellList, freeHoleList) {
|
||||
for (const hole of freeHoleList)
|
||||
if (hole.getShell() === null) {
|
||||
const shell = hole.findEdgeRingContaining(shellList)
|
||||
if (this._isEnforcePolygonal && shell === null)
|
||||
throw new TopologyException('unable to assign free hole to a shell', hole.getCoordinate())
|
||||
|
||||
hole.setShell(shell)
|
||||
}
|
||||
|
||||
}
|
||||
getShellRings() {
|
||||
return this._shellList
|
||||
}
|
||||
getPolygons() {
|
||||
return this.computePolygons(this._shellList)
|
||||
}
|
||||
linkResultAreaEdgesMax(resultEdges) {
|
||||
for (const edge of resultEdges)
|
||||
MaximalEdgeRing.linkResultAreaMaxRingAtNode(edge)
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
import IllegalArgumentException from '../../../../../java/lang/IllegalArgumentException.js'
|
||||
import OverlayNG from './OverlayNG.js'
|
||||
import TopologyException from '../../geom/TopologyException.js'
|
||||
export default class PrecisionReducer {
|
||||
static reducePrecision(geom, pm) {
|
||||
const ov = new OverlayNG(geom, pm)
|
||||
if (geom.getDimension() === 2)
|
||||
ov.setAreaResultOnly(true)
|
||||
|
||||
try {
|
||||
const reduced = ov.getResult()
|
||||
return reduced
|
||||
} catch (ex) {
|
||||
if (ex instanceof TopologyException)
|
||||
throw new IllegalArgumentException('Reduction failed, possible invalid input')
|
||||
else throw ex
|
||||
} finally {}
|
||||
}
|
||||
}
|
||||
@ -1,125 +0,0 @@
|
||||
import Geometry from '../../geom/Geometry.js'
|
||||
import CoordinateFilter from '../../geom/CoordinateFilter.js'
|
||||
import OrdinateFormat from '../../io/OrdinateFormat.js'
|
||||
import MathUtil from '../../math/MathUtil.js'
|
||||
import PrecisionModel from '../../geom/PrecisionModel.js'
|
||||
export default class PrecisionUtil {
|
||||
static robustScale() {
|
||||
if (arguments.length === 1) {
|
||||
const a = arguments[0]
|
||||
const inherentScale = PrecisionUtil.inherentScale(a)
|
||||
const safeScale = PrecisionUtil.safeScale(a)
|
||||
return PrecisionUtil.robustScale(inherentScale, safeScale)
|
||||
} else if (arguments.length === 2) {
|
||||
if (arguments[0] instanceof Geometry && arguments[1] instanceof Geometry) {
|
||||
const a = arguments[0], b = arguments[1]
|
||||
const inherentScale = PrecisionUtil.inherentScale(a, b)
|
||||
const safeScale = PrecisionUtil.safeScale(a, b)
|
||||
return PrecisionUtil.robustScale(inherentScale, safeScale)
|
||||
} else if (typeof arguments[0] === 'number' && typeof arguments[1] === 'number') {
|
||||
const inherentScale = arguments[0], safeScale = arguments[1]
|
||||
if (inherentScale <= safeScale)
|
||||
return inherentScale
|
||||
|
||||
return safeScale
|
||||
}
|
||||
}
|
||||
}
|
||||
static precisionScale(value, precisionDigits) {
|
||||
const magnitude = Math.trunc(Math.log(value) / Math.log(10) + 1.0)
|
||||
const precDigits = precisionDigits - magnitude
|
||||
const scaleFactor = Math.pow(10.0, precDigits)
|
||||
return scaleFactor
|
||||
}
|
||||
static safeScale() {
|
||||
if (arguments.length === 1) {
|
||||
if (typeof arguments[0] === 'number') {
|
||||
const value = arguments[0]
|
||||
return PrecisionUtil.precisionScale(value, PrecisionUtil.MAX_ROBUST_DP_DIGITS)
|
||||
} else if (arguments[0] instanceof Geometry) {
|
||||
const geom = arguments[0]
|
||||
return PrecisionUtil.safeScale(PrecisionUtil.maxBoundMagnitude(geom.getEnvelopeInternal()))
|
||||
}
|
||||
} else if (arguments.length === 2) {
|
||||
const a = arguments[0], b = arguments[1]
|
||||
let maxBnd = PrecisionUtil.maxBoundMagnitude(a.getEnvelopeInternal())
|
||||
if (b !== null) {
|
||||
const maxBndB = PrecisionUtil.maxBoundMagnitude(b.getEnvelopeInternal())
|
||||
maxBnd = Math.max(maxBnd, maxBndB)
|
||||
}
|
||||
const scale = PrecisionUtil.safeScale(maxBnd)
|
||||
return scale
|
||||
}
|
||||
}
|
||||
static inherentScale() {
|
||||
if (arguments.length === 1) {
|
||||
if (typeof arguments[0] === 'number') {
|
||||
const value = arguments[0]
|
||||
const numDec = PrecisionUtil.numberOfDecimals(value)
|
||||
const scaleFactor = Math.pow(10.0, numDec)
|
||||
return scaleFactor
|
||||
} else if (arguments[0] instanceof Geometry) {
|
||||
const geom = arguments[0]
|
||||
const scaleFilter = new InherentScaleFilter()
|
||||
geom.apply(scaleFilter)
|
||||
return scaleFilter.getScale()
|
||||
}
|
||||
} else if (arguments.length === 2) {
|
||||
const a = arguments[0], b = arguments[1]
|
||||
let scale = PrecisionUtil.inherentScale(a)
|
||||
if (b !== null) {
|
||||
const scaleB = PrecisionUtil.inherentScale(b)
|
||||
scale = Math.max(scale, scaleB)
|
||||
}
|
||||
return scale
|
||||
}
|
||||
}
|
||||
static numberOfDecimals(value) {
|
||||
const s = OrdinateFormat.DEFAULT.format(value)
|
||||
if (s.endsWith('.0')) return 0
|
||||
const len = s.length
|
||||
const decIndex = s.indexOf('.')
|
||||
if (decIndex <= 0) return 0
|
||||
return len - decIndex - 1
|
||||
}
|
||||
static maxBoundMagnitude(env) {
|
||||
return MathUtil.max(Math.abs(env.getMaxX()), Math.abs(env.getMaxY()), Math.abs(env.getMinX()), Math.abs(env.getMinY()))
|
||||
}
|
||||
static robustPM() {
|
||||
if (arguments.length === 1) {
|
||||
const a = arguments[0]
|
||||
const scale = PrecisionUtil.robustScale(a)
|
||||
return new PrecisionModel(scale)
|
||||
} else if (arguments.length === 2) {
|
||||
const a = arguments[0], b = arguments[1]
|
||||
const scale = PrecisionUtil.robustScale(a, b)
|
||||
return new PrecisionModel(scale)
|
||||
}
|
||||
}
|
||||
}
|
||||
class InherentScaleFilter {
|
||||
constructor() {
|
||||
InherentScaleFilter.constructor_.apply(this, arguments)
|
||||
}
|
||||
static constructor_() {
|
||||
this._scale = 0
|
||||
}
|
||||
getScale() {
|
||||
return this._scale
|
||||
}
|
||||
filter(coord) {
|
||||
this.updateScaleMax(coord.getX())
|
||||
this.updateScaleMax(coord.getY())
|
||||
}
|
||||
updateScaleMax(value) {
|
||||
const scaleVal = PrecisionUtil.inherentScale(value)
|
||||
if (scaleVal > this._scale)
|
||||
this._scale = scaleVal
|
||||
|
||||
}
|
||||
get interfaces_() {
|
||||
return [CoordinateFilter]
|
||||
}
|
||||
}
|
||||
PrecisionUtil.InherentScaleFilter = InherentScaleFilter
|
||||
PrecisionUtil.MAX_ROBUST_DP_DIGITS = 14
|
||||
@ -1,103 +0,0 @@
|
||||
import CoordinateList from '../../geom/CoordinateList.js'
|
||||
import Coordinate from '../../geom/Coordinate.js'
|
||||
export default class RingClipper {
|
||||
constructor() {
|
||||
RingClipper.constructor_.apply(this, arguments)
|
||||
}
|
||||
static constructor_() {
|
||||
this._clipEnv = null
|
||||
this._clipEnvMinY = null
|
||||
this._clipEnvMaxY = null
|
||||
this._clipEnvMinX = null
|
||||
this._clipEnvMaxX = null
|
||||
const clipEnv = arguments[0]
|
||||
this._clipEnv = clipEnv
|
||||
this._clipEnvMinY = clipEnv.getMinY()
|
||||
this._clipEnvMaxY = clipEnv.getMaxY()
|
||||
this._clipEnvMinX = clipEnv.getMinX()
|
||||
this._clipEnvMaxX = clipEnv.getMaxX()
|
||||
}
|
||||
intersectionLineX(a, b, x) {
|
||||
const m = (b.y - a.y) / (b.x - a.x)
|
||||
const intercept = (x - a.x) * m
|
||||
return a.y + intercept
|
||||
}
|
||||
clipToBoxEdge(pts, edgeIndex, closeRing) {
|
||||
const ptsClip = new CoordinateList()
|
||||
let p0 = pts[pts.length - 1]
|
||||
for (let i = 0; i < pts.length; i++) {
|
||||
const p1 = pts[i]
|
||||
if (this.isInsideEdge(p1, edgeIndex)) {
|
||||
if (!this.isInsideEdge(p0, edgeIndex)) {
|
||||
const intPt = this.intersection(p0, p1, edgeIndex)
|
||||
ptsClip.add(intPt, false)
|
||||
}
|
||||
ptsClip.add(p1.copy(), false)
|
||||
} else if (this.isInsideEdge(p0, edgeIndex)) {
|
||||
const intPt = this.intersection(p0, p1, edgeIndex)
|
||||
ptsClip.add(intPt, false)
|
||||
}
|
||||
p0 = p1
|
||||
}
|
||||
if (closeRing && ptsClip.size() > 0) {
|
||||
const start = ptsClip.get(0)
|
||||
if (!start.equals2D(ptsClip.get(ptsClip.size() - 1)))
|
||||
ptsClip.add(start.copy())
|
||||
|
||||
}
|
||||
return ptsClip.toCoordinateArray()
|
||||
}
|
||||
intersection(a, b, edgeIndex) {
|
||||
let intPt = null
|
||||
switch (edgeIndex) {
|
||||
case RingClipper.BOX_BOTTOM:
|
||||
intPt = new Coordinate(this.intersectionLineY(a, b, this._clipEnvMinY), this._clipEnvMinY)
|
||||
break
|
||||
case RingClipper.BOX_RIGHT:
|
||||
intPt = new Coordinate(this._clipEnvMaxX, this.intersectionLineX(a, b, this._clipEnvMaxX))
|
||||
break
|
||||
case RingClipper.BOX_TOP:
|
||||
intPt = new Coordinate(this.intersectionLineY(a, b, this._clipEnvMaxY), this._clipEnvMaxY)
|
||||
break
|
||||
case RingClipper.BOX_LEFT:
|
||||
default:
|
||||
intPt = new Coordinate(this._clipEnvMinX, this.intersectionLineX(a, b, this._clipEnvMinX))
|
||||
}
|
||||
return intPt
|
||||
}
|
||||
intersectionLineY(a, b, y) {
|
||||
const m = (b.x - a.x) / (b.y - a.y)
|
||||
const intercept = (y - a.y) * m
|
||||
return a.x + intercept
|
||||
}
|
||||
isInsideEdge(p, edgeIndex) {
|
||||
let isInside = false
|
||||
switch (edgeIndex) {
|
||||
case RingClipper.BOX_BOTTOM:
|
||||
isInside = p.y > this._clipEnvMinY
|
||||
break
|
||||
case RingClipper.BOX_RIGHT:
|
||||
isInside = p.x < this._clipEnvMaxX
|
||||
break
|
||||
case RingClipper.BOX_TOP:
|
||||
isInside = p.y < this._clipEnvMaxY
|
||||
break
|
||||
case RingClipper.BOX_LEFT:
|
||||
default:
|
||||
isInside = p.x > this._clipEnvMinX
|
||||
}
|
||||
return isInside
|
||||
}
|
||||
clip(pts) {
|
||||
for (let edgeIndex = 0; edgeIndex < 4; edgeIndex++) {
|
||||
const closeRing = edgeIndex === 3
|
||||
pts = this.clipToBoxEdge(pts, edgeIndex, closeRing)
|
||||
if (pts.length === 0) return pts
|
||||
}
|
||||
return pts
|
||||
}
|
||||
}
|
||||
RingClipper.BOX_LEFT = 3
|
||||
RingClipper.BOX_TOP = 2
|
||||
RingClipper.BOX_RIGHT = 1
|
||||
RingClipper.BOX_BOTTOM = 0
|
||||
@ -1,57 +0,0 @@
|
||||
import Polygon from '../../geom/Polygon.js'
|
||||
import GeometryCollection from '../../geom/GeometryCollection.js'
|
||||
export default class RobustClipEnvelopeComputer {
|
||||
constructor() {
|
||||
RobustClipEnvelopeComputer.constructor_.apply(this, arguments)
|
||||
}
|
||||
static constructor_() {
|
||||
this._targetEnv = null
|
||||
this._clipEnv = null
|
||||
const targetEnv = arguments[0]
|
||||
this._targetEnv = targetEnv
|
||||
this._clipEnv = targetEnv.copy()
|
||||
}
|
||||
static getEnvelope(a, b, targetEnv) {
|
||||
const cec = new RobustClipEnvelopeComputer(targetEnv)
|
||||
cec.add(a)
|
||||
cec.add(b)
|
||||
return cec.getEnvelope()
|
||||
}
|
||||
static intersectsSegment(env, p1, p2) {
|
||||
return env.intersects(p1, p2)
|
||||
}
|
||||
addPolygon(poly) {
|
||||
const shell = poly.getExteriorRing()
|
||||
this.addPolygonRing(shell)
|
||||
for (let i = 0; i < poly.getNumInteriorRing(); i++) {
|
||||
const hole = poly.getInteriorRingN(i)
|
||||
this.addPolygonRing(hole)
|
||||
}
|
||||
}
|
||||
addSegment(p1, p2) {
|
||||
if (RobustClipEnvelopeComputer.intersectsSegment(this._targetEnv, p1, p2)) {
|
||||
this._clipEnv.expandToInclude(p1)
|
||||
this._clipEnv.expandToInclude(p2)
|
||||
}
|
||||
}
|
||||
getEnvelope() {
|
||||
return this._clipEnv
|
||||
}
|
||||
addPolygonRing(ring) {
|
||||
if (ring.isEmpty()) return null
|
||||
const seq = ring.getCoordinateSequence()
|
||||
for (let i = 1; i < seq.size(); i++)
|
||||
this.addSegment(seq.getCoordinate(i - 1), seq.getCoordinate(i))
|
||||
|
||||
}
|
||||
add(g) {
|
||||
if (g === null || g.isEmpty()) return null
|
||||
if (g instanceof Polygon) this.addPolygon(g); else if (g instanceof GeometryCollection) this.addCollection(g)
|
||||
}
|
||||
addCollection(gc) {
|
||||
for (let i = 0; i < gc.getNumGeometries(); i++) {
|
||||
const g = gc.getGeometryN(i)
|
||||
this.add(g)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,45 +0,0 @@
|
||||
import Geometry from '../../geom/Geometry.js'
|
||||
import hasInterface from '../../../../../hasInterface.js'
|
||||
import UnionStrategy from '../union/UnionStrategy.js'
|
||||
import Collection from '../../../../../java/util/Collection.js'
|
||||
import OverlayNG from './OverlayNG.js'
|
||||
import UnaryUnionOp from '../union/UnaryUnionOp.js'
|
||||
import OverlayUtil from './OverlayUtil.js'
|
||||
import PrecisionModel from '../../geom/PrecisionModel.js'
|
||||
import UNION from './OverlayNG/UNION.js'
|
||||
export default class UnaryUnionNG {
|
||||
static union() {
|
||||
if (arguments.length === 2) {
|
||||
if (arguments[0] instanceof Geometry && arguments[1] instanceof PrecisionModel) {
|
||||
const geom = arguments[0], pm = arguments[1]
|
||||
const op = new UnaryUnionOp(geom)
|
||||
op.setUnionFunction(UnaryUnionNG.createUnionStrategy(pm))
|
||||
return op.union()
|
||||
} else if (hasInterface(arguments[0], Collection) && arguments[1] instanceof PrecisionModel) {
|
||||
const geoms = arguments[0], pm = arguments[1]
|
||||
const op = new UnaryUnionOp(geoms)
|
||||
op.setUnionFunction(UnaryUnionNG.createUnionStrategy(pm))
|
||||
return op.union()
|
||||
}
|
||||
} else if (arguments.length === 3) {
|
||||
const geoms = arguments[0], geomFact = arguments[1], pm = arguments[2]
|
||||
const op = new UnaryUnionOp(geoms, geomFact)
|
||||
op.setUnionFunction(UnaryUnionNG.createUnionStrategy(pm))
|
||||
return op.union()
|
||||
}
|
||||
}
|
||||
static createUnionStrategy(pm) {
|
||||
const unionSRFun = new (class {
|
||||
get interfaces_() {
|
||||
return [UnionStrategy]
|
||||
}
|
||||
union(g0, g1) {
|
||||
return OverlayNG.overlay(g0, g1, OverlayNG.UNION, pm)
|
||||
}
|
||||
isFloatingPrecision() {
|
||||
return OverlayUtil.isFloating(pm)
|
||||
}
|
||||
})()
|
||||
return unionSRFun
|
||||
}
|
||||
}
|
||||
@ -127,7 +127,7 @@ export default class RelateComputer {
|
||||
const ei = eiIt.next()
|
||||
const n = this._nodes.addNode(ei.coord)
|
||||
if (eLoc === Location.BOUNDARY) n.setLabelBoundary(argIndex); else
|
||||
if (n.getLabel().isNull(argIndex)) n.setLabel(argIndex, Location.INTERIOR)
|
||||
if (n.getLabel().isNull(argIndex)) n.setLabel(argIndex, Location.INTERIOR)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,7 +40,7 @@ export default class RelateNodeGraph {
|
||||
const ei = eiIt.next()
|
||||
const n = this._nodes.addNode(ei.coord)
|
||||
if (eLoc === Location.BOUNDARY) n.setLabelBoundary(argIndex); else
|
||||
if (n.getLabel().isNull(argIndex)) n.setLabel(argIndex, Location.INTERIOR)
|
||||
if (n.getLabel().isNull(argIndex)) n.setLabel(argIndex, Location.INTERIOR)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +0,0 @@
|
||||
export default class UnionStrategy {
|
||||
union(g0, g1) {}
|
||||
isFloatingPrecision() {}
|
||||
}
|
||||
@ -1,41 +0,0 @@
|
||||
import STRtree from '../../index/strtree/STRtree.js'
|
||||
import PolygonTopologyAnalyzer from './PolygonTopologyAnalyzer.js'
|
||||
export default class IndexedNestedHoleTester {
|
||||
constructor() {
|
||||
IndexedNestedHoleTester.constructor_.apply(this, arguments)
|
||||
}
|
||||
static constructor_() {
|
||||
this._polygon = null
|
||||
this._index = null
|
||||
this._nestedPt = null
|
||||
const poly = arguments[0]
|
||||
this._polygon = poly
|
||||
this.loadIndex()
|
||||
}
|
||||
loadIndex() {
|
||||
this._index = new STRtree()
|
||||
for (let i = 0; i < this._polygon.getNumInteriorRing(); i++) {
|
||||
const hole = this._polygon.getInteriorRingN(i)
|
||||
const env = hole.getEnvelopeInternal()
|
||||
this._index.insert(env, hole)
|
||||
}
|
||||
}
|
||||
getNestedPoint() {
|
||||
return this._nestedPt
|
||||
}
|
||||
isNested() {
|
||||
for (let i = 0; i < this._polygon.getNumInteriorRing(); i++) {
|
||||
const hole = this._polygon.getInteriorRingN(i)
|
||||
const results = this._index.query(hole.getEnvelopeInternal())
|
||||
for (const testHole of results) {
|
||||
if (hole === testHole) continue
|
||||
if (!testHole.getEnvelopeInternal().covers(hole.getEnvelopeInternal())) continue
|
||||
if (PolygonTopologyAnalyzer.isRingNested(hole, testHole)) {
|
||||
this._nestedPt = hole.getCoordinateN(0)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
@ -1,82 +0,0 @@
|
||||
import Location from '../../geom/Location.js'
|
||||
import STRtree from '../../index/strtree/STRtree.js'
|
||||
import PolygonTopologyAnalyzer from './PolygonTopologyAnalyzer.js'
|
||||
import IndexedPointInAreaLocator from '../../algorithm/locate/IndexedPointInAreaLocator.js'
|
||||
export default class IndexedNestedPolygonTester {
|
||||
constructor() {
|
||||
IndexedNestedPolygonTester.constructor_.apply(this, arguments)
|
||||
}
|
||||
static constructor_() {
|
||||
this._multiPoly = null
|
||||
this._index = null
|
||||
this._locators = null
|
||||
this._nestedPt = null
|
||||
const multiPoly = arguments[0]
|
||||
this._multiPoly = multiPoly
|
||||
this.loadIndex()
|
||||
}
|
||||
static findIncidentSegmentNestedPoint(shell, poly) {
|
||||
const polyShell = poly.getExteriorRing()
|
||||
if (polyShell.isEmpty()) return null
|
||||
if (!PolygonTopologyAnalyzer.isRingNested(shell, polyShell)) return null
|
||||
for (let i = 0; i < poly.getNumInteriorRing(); i++) {
|
||||
const hole = poly.getInteriorRingN(i)
|
||||
if (hole.getEnvelopeInternal().covers(shell.getEnvelopeInternal()) && PolygonTopologyAnalyzer.isRingNested(shell, hole))
|
||||
return null
|
||||
|
||||
}
|
||||
return shell.getCoordinateN(0)
|
||||
}
|
||||
getNestedPoint() {
|
||||
return this._nestedPt
|
||||
}
|
||||
loadIndex() {
|
||||
this._index = new STRtree()
|
||||
for (let i = 0; i < this._multiPoly.getNumGeometries(); i++) {
|
||||
const poly = this._multiPoly.getGeometryN(i)
|
||||
const env = poly.getEnvelopeInternal()
|
||||
this._index.insert(env, i)
|
||||
}
|
||||
}
|
||||
isNested() {
|
||||
for (let i = 0; i < this._multiPoly.getNumGeometries(); i++) {
|
||||
const poly = this._multiPoly.getGeometryN(i)
|
||||
const shell = poly.getExteriorRing()
|
||||
const results = this._index.query(poly.getEnvelopeInternal())
|
||||
for (const polyIndex of results) {
|
||||
const possibleOuterPoly = this._multiPoly.getGeometryN(polyIndex)
|
||||
if (poly === possibleOuterPoly) continue
|
||||
if (!possibleOuterPoly.getEnvelopeInternal().covers(poly.getEnvelopeInternal())) continue
|
||||
this._nestedPt = this.findNestedPoint(shell, possibleOuterPoly, this.getLocator(polyIndex))
|
||||
if (this._nestedPt !== null) return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
getLocator(polyIndex) {
|
||||
if (this._locators === null)
|
||||
this._locators = new Array(this._multiPoly.getNumGeometries()).fill(null)
|
||||
|
||||
let locator = this._locators[polyIndex]
|
||||
if (locator === null) {
|
||||
locator = new IndexedPointInAreaLocator(this._multiPoly.getGeometryN(polyIndex))
|
||||
this._locators[polyIndex] = locator
|
||||
}
|
||||
return locator
|
||||
}
|
||||
findNestedPoint(shell, possibleOuterPoly, locator) {
|
||||
const shellPt0 = shell.getCoordinateN(0)
|
||||
const loc0 = locator.locate(shellPt0)
|
||||
if (loc0 === Location.EXTERIOR) return null
|
||||
if (loc0 === Location.INTERIOR)
|
||||
return shellPt0
|
||||
|
||||
const shellPt1 = shell.getCoordinateN(1)
|
||||
const loc1 = locator.locate(shellPt1)
|
||||
if (loc1 === Location.EXTERIOR) return null
|
||||
if (loc1 === Location.INTERIOR)
|
||||
return shellPt1
|
||||
|
||||
return IndexedNestedPolygonTester.findIncidentSegmentNestedPoint(shell, possibleOuterPoly)
|
||||
}
|
||||
}
|
||||
@ -1,232 +0,0 @@
|
||||
import BasicSegmentString from '../../noding/BasicSegmentString.js'
|
||||
import LineString from '../../geom/LineString.js'
|
||||
import HashSet from '../../../../../java/util/HashSet.js'
|
||||
import hasInterface from '../../../../../hasInterface.js'
|
||||
import MCIndexNoder from '../../noding/MCIndexNoder.js'
|
||||
import Point from '../../geom/Point.js'
|
||||
import MultiPoint from '../../geom/MultiPoint.js'
|
||||
import BoundaryNodeRule from '../../algorithm/BoundaryNodeRule.js'
|
||||
import SegmentIntersector from '../../noding/SegmentIntersector.js'
|
||||
import GeometryCollection from '../../geom/GeometryCollection.js'
|
||||
import CoordinateArrays from '../../geom/CoordinateArrays.js'
|
||||
import Polygonal from '../../geom/Polygonal.js'
|
||||
import ArrayList from '../../../../../java/util/ArrayList.js'
|
||||
import RobustLineIntersector from '../../algorithm/RobustLineIntersector.js'
|
||||
import LinearComponentExtracter from '../../geom/util/LinearComponentExtracter.js'
|
||||
import MultiLineString from '../../geom/MultiLineString.js'
|
||||
export default class IsSimpleOp {
|
||||
constructor() {
|
||||
IsSimpleOp.constructor_.apply(this, arguments)
|
||||
}
|
||||
static constructor_() {
|
||||
this._inputGeom = null
|
||||
this._isClosedEndpointsInInterior = null
|
||||
this._isFindAllLocations = null
|
||||
this._isSimple = false
|
||||
this._nonSimplePts = null
|
||||
if (arguments.length === 1) {
|
||||
const geom = arguments[0]
|
||||
IsSimpleOp.constructor_.call(this, geom, BoundaryNodeRule.MOD2_BOUNDARY_RULE)
|
||||
} else if (arguments.length === 2) {
|
||||
const geom = arguments[0], boundaryNodeRule = arguments[1]
|
||||
this._inputGeom = geom
|
||||
this._isClosedEndpointsInInterior = !boundaryNodeRule.isInBoundary(2)
|
||||
}
|
||||
}
|
||||
static isSimple(geom) {
|
||||
const op = new IsSimpleOp(geom)
|
||||
return op.isSimple()
|
||||
}
|
||||
static getNonSimpleLocation(geom) {
|
||||
const op = new IsSimpleOp(geom)
|
||||
return op.getNonSimpleLocation()
|
||||
}
|
||||
static extractSegmentStrings(geom) {
|
||||
const segStrings = new ArrayList()
|
||||
for (let i = 0; i < geom.getNumGeometries(); i++) {
|
||||
const line = geom.getGeometryN(i)
|
||||
const trimPts = IsSimpleOp.trimRepeatedPoints(line.getCoordinates())
|
||||
if (trimPts !== null) {
|
||||
const ss = new BasicSegmentString(trimPts, null)
|
||||
segStrings.add(ss)
|
||||
}
|
||||
}
|
||||
return segStrings
|
||||
}
|
||||
static trimRepeatedPoints(pts) {
|
||||
if (pts.length <= 2) return pts
|
||||
const len = pts.length
|
||||
const hasRepeatedStart = pts[0].equals2D(pts[1])
|
||||
const hasRepeatedEnd = pts[len - 1].equals2D(pts[len - 2])
|
||||
if (!hasRepeatedStart && !hasRepeatedEnd) return pts
|
||||
let startIndex = 0
|
||||
const startPt = pts[0]
|
||||
while (startIndex < len - 1 && startPt.equals2D(pts[startIndex + 1]))
|
||||
startIndex++
|
||||
|
||||
let endIndex = len - 1
|
||||
const endPt = pts[endIndex]
|
||||
while (endIndex > 0 && endPt.equals2D(pts[endIndex - 1]))
|
||||
endIndex--
|
||||
|
||||
if (endIndex - startIndex < 1)
|
||||
return null
|
||||
|
||||
const trimPts = CoordinateArrays.extract(pts, startIndex, endIndex)
|
||||
return trimPts
|
||||
}
|
||||
isSimpleMultiPoint(mp) {
|
||||
if (mp.isEmpty()) return true
|
||||
let isSimple = true
|
||||
const points = new HashSet()
|
||||
for (let i = 0; i < mp.getNumGeometries(); i++) {
|
||||
const pt = mp.getGeometryN(i)
|
||||
const p = pt.getCoordinate()
|
||||
if (points.contains(p)) {
|
||||
this._nonSimplePts.add(p)
|
||||
isSimple = false
|
||||
if (!this._isFindAllLocations) break
|
||||
} else {
|
||||
points.add(p)
|
||||
}
|
||||
}
|
||||
return isSimple
|
||||
}
|
||||
isSimplePolygonal(geom) {
|
||||
let isSimple = true
|
||||
const rings = LinearComponentExtracter.getLines(geom)
|
||||
for (const ring of rings)
|
||||
if (!this.isSimpleLinearGeometry(ring)) {
|
||||
isSimple = false
|
||||
if (!this._isFindAllLocations) break
|
||||
}
|
||||
|
||||
return isSimple
|
||||
}
|
||||
compute() {
|
||||
if (this._nonSimplePts !== null) return null
|
||||
this._nonSimplePts = new ArrayList()
|
||||
this._isSimple = this.computeSimple(this._inputGeom)
|
||||
}
|
||||
getNonSimpleLocation() {
|
||||
this.compute()
|
||||
if (this._nonSimplePts.size() === 0) return null
|
||||
return this._nonSimplePts.get(0)
|
||||
}
|
||||
getNonSimpleLocations() {
|
||||
this.compute()
|
||||
return this._nonSimplePts
|
||||
}
|
||||
isSimpleLinearGeometry(geom) {
|
||||
if (geom.isEmpty()) return true
|
||||
const segStrings = IsSimpleOp.extractSegmentStrings(geom)
|
||||
const segInt = new NonSimpleIntersectionFinder(this._isClosedEndpointsInInterior, this._isFindAllLocations, this._nonSimplePts)
|
||||
const noder = new MCIndexNoder()
|
||||
noder.setSegmentIntersector(segInt)
|
||||
noder.computeNodes(segStrings)
|
||||
if (segInt.hasIntersection())
|
||||
return false
|
||||
|
||||
return true
|
||||
}
|
||||
setFindAllLocations(isFindAll) {
|
||||
this._isFindAllLocations = isFindAll
|
||||
}
|
||||
computeSimple(geom) {
|
||||
if (geom.isEmpty()) return true
|
||||
if (geom instanceof Point) return true
|
||||
if (geom instanceof LineString) return this.isSimpleLinearGeometry(geom)
|
||||
if (geom instanceof MultiLineString) return this.isSimpleLinearGeometry(geom)
|
||||
if (geom instanceof MultiPoint) return this.isSimpleMultiPoint(geom)
|
||||
if (hasInterface(geom, Polygonal)) return this.isSimplePolygonal(geom)
|
||||
if (geom instanceof GeometryCollection) return this.isSimpleGeometryCollection(geom)
|
||||
return true
|
||||
}
|
||||
isSimple() {
|
||||
this.compute()
|
||||
return this._isSimple
|
||||
}
|
||||
isSimpleGeometryCollection(geom) {
|
||||
let isSimple = true
|
||||
for (let i = 0; i < geom.getNumGeometries(); i++) {
|
||||
const comp = geom.getGeometryN(i)
|
||||
if (!this.computeSimple(comp)) {
|
||||
isSimple = false
|
||||
if (!this._isFindAllLocations) break
|
||||
}
|
||||
}
|
||||
return isSimple
|
||||
}
|
||||
}
|
||||
class NonSimpleIntersectionFinder {
|
||||
constructor() {
|
||||
NonSimpleIntersectionFinder.constructor_.apply(this, arguments)
|
||||
}
|
||||
static constructor_() {
|
||||
this._isClosedEndpointsInInterior = null
|
||||
this._isFindAll = null
|
||||
this.li = new RobustLineIntersector()
|
||||
this._intersectionPts = null
|
||||
const isClosedEndpointsInInterior = arguments[0], isFindAll = arguments[1], intersectionPts = arguments[2]
|
||||
this._isClosedEndpointsInInterior = isClosedEndpointsInInterior
|
||||
this._isFindAll = isFindAll
|
||||
this._intersectionPts = intersectionPts
|
||||
}
|
||||
static isIntersectionEndpoint(ss, ssIndex, li, liSegmentIndex) {
|
||||
const vertexIndex = NonSimpleIntersectionFinder.intersectionVertexIndex(li, liSegmentIndex)
|
||||
if (vertexIndex === 0)
|
||||
return ssIndex === 0
|
||||
else
|
||||
return ssIndex + 2 === ss.size()
|
||||
|
||||
}
|
||||
static intersectionVertexIndex(li, segmentIndex) {
|
||||
const intPt = li.getIntersection(0)
|
||||
const endPt0 = li.getEndpoint(segmentIndex, 0)
|
||||
return intPt.equals2D(endPt0) ? 0 : 1
|
||||
}
|
||||
hasIntersection() {
|
||||
return this._intersectionPts.size() > 0
|
||||
}
|
||||
processIntersections(ss0, segIndex0, ss1, segIndex1) {
|
||||
const isSameSegString = ss0 === ss1
|
||||
const isSameSegment = isSameSegString && segIndex0 === segIndex1
|
||||
if (isSameSegment) return null
|
||||
const hasInt = this.findIntersection(ss0, segIndex0, ss1, segIndex1)
|
||||
if (hasInt)
|
||||
this._intersectionPts.add(this.li.getIntersection(0))
|
||||
|
||||
}
|
||||
findIntersection(ss0, segIndex0, ss1, segIndex1) {
|
||||
const p00 = ss0.getCoordinate(segIndex0)
|
||||
const p01 = ss0.getCoordinate(segIndex0 + 1)
|
||||
const p10 = ss1.getCoordinate(segIndex1)
|
||||
const p11 = ss1.getCoordinate(segIndex1 + 1)
|
||||
this.li.computeIntersection(p00, p01, p10, p11)
|
||||
if (!this.li.hasIntersection()) return false
|
||||
const hasInteriorInt = this.li.isInteriorIntersection()
|
||||
if (hasInteriorInt) return true
|
||||
const hasEqualSegments = this.li.getIntersectionNum() >= 2
|
||||
if (hasEqualSegments) return true
|
||||
const isSameSegString = ss0 === ss1
|
||||
const isAdjacentSegment = isSameSegString && Math.abs(segIndex1 - segIndex0) <= 1
|
||||
if (isAdjacentSegment) return false
|
||||
const isIntersectionEndpt0 = NonSimpleIntersectionFinder.isIntersectionEndpoint(ss0, segIndex0, this.li, 0)
|
||||
const isIntersectionEndpt1 = NonSimpleIntersectionFinder.isIntersectionEndpoint(ss1, segIndex1, this.li, 1)
|
||||
const hasInteriorVertexInt = !(isIntersectionEndpt0 && isIntersectionEndpt1)
|
||||
if (hasInteriorVertexInt) return true
|
||||
if (this._isClosedEndpointsInInterior && !isSameSegString) {
|
||||
const hasInteriorEndpointInt = ss0.isClosed() || ss1.isClosed()
|
||||
if (hasInteriorEndpointInt) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
isDone() {
|
||||
if (this._isFindAll) return false
|
||||
return this._intersectionPts.size() > 0
|
||||
}
|
||||
get interfaces_() {
|
||||
return [SegmentIntersector]
|
||||
}
|
||||
}
|
||||
IsSimpleOp.NonSimpleIntersectionFinder = NonSimpleIntersectionFinder
|
||||
@ -1,122 +0,0 @@
|
||||
import PolygonNodeTopology from '../../algorithm/PolygonNodeTopology.js'
|
||||
import SegmentIntersector from '../../noding/SegmentIntersector.js'
|
||||
import PolygonRing from './PolygonRing.js'
|
||||
import RobustLineIntersector from '../../algorithm/RobustLineIntersector.js'
|
||||
import TopologyValidationError from './TopologyValidationError.js'
|
||||
import IllegalStateException from '../../../../../java/lang/IllegalStateException.js'
|
||||
export default class PolygonIntersectionAnalyzer {
|
||||
constructor() {
|
||||
PolygonIntersectionAnalyzer.constructor_.apply(this, arguments)
|
||||
}
|
||||
static constructor_() {
|
||||
this._isInvertedRingValid = null
|
||||
this._li = new RobustLineIntersector()
|
||||
this._invalidCode = PolygonIntersectionAnalyzer.NO_INVALID_INTERSECTION
|
||||
this._invalidLocation = null
|
||||
this._hasDoubleTouch = false
|
||||
this._doubleTouchLocation = null
|
||||
const isInvertedRingValid = arguments[0]
|
||||
this._isInvertedRingValid = isInvertedRingValid
|
||||
}
|
||||
static prevCoordinateInRing(ringSS, segIndex) {
|
||||
let prevIndex = segIndex - 1
|
||||
if (prevIndex < 0)
|
||||
prevIndex = ringSS.size() - 2
|
||||
|
||||
return ringSS.getCoordinate(prevIndex)
|
||||
}
|
||||
static isAdjacentInRing(ringSS, segIndex0, segIndex1) {
|
||||
const delta = Math.abs(segIndex1 - segIndex0)
|
||||
if (delta <= 1) return true
|
||||
if (delta >= ringSS.size() - 2) return true
|
||||
return false
|
||||
}
|
||||
getDoubleTouchLocation() {
|
||||
return this._doubleTouchLocation
|
||||
}
|
||||
hasDoubleTouch() {
|
||||
return this._hasDoubleTouch
|
||||
}
|
||||
addSelfTouch(ss, intPt, e00, e01, e10, e11) {
|
||||
const polyRing = ss.getData()
|
||||
if (polyRing === null)
|
||||
throw new IllegalStateException('SegmentString missing PolygonRing data when checking self-touches')
|
||||
|
||||
polyRing.addSelfTouch(intPt, e00, e01, e10, e11)
|
||||
}
|
||||
findInvalidIntersection(ss0, segIndex0, ss1, segIndex1) {
|
||||
const p00 = ss0.getCoordinate(segIndex0)
|
||||
const p01 = ss0.getCoordinate(segIndex0 + 1)
|
||||
const p10 = ss1.getCoordinate(segIndex1)
|
||||
const p11 = ss1.getCoordinate(segIndex1 + 1)
|
||||
this._li.computeIntersection(p00, p01, p10, p11)
|
||||
if (!this._li.hasIntersection())
|
||||
return PolygonIntersectionAnalyzer.NO_INVALID_INTERSECTION
|
||||
|
||||
const isSameSegString = ss0 === ss1
|
||||
if (this._li.isProper() || this._li.getIntersectionNum() >= 2)
|
||||
return TopologyValidationError.SELF_INTERSECTION
|
||||
|
||||
const intPt = this._li.getIntersection(0)
|
||||
const isAdjacentSegments = isSameSegString && PolygonIntersectionAnalyzer.isAdjacentInRing(ss0, segIndex0, segIndex1)
|
||||
if (isAdjacentSegments) return PolygonIntersectionAnalyzer.NO_INVALID_INTERSECTION
|
||||
if (isSameSegString && !this._isInvertedRingValid)
|
||||
return TopologyValidationError.RING_SELF_INTERSECTION
|
||||
|
||||
if (intPt.equals2D(p01) || intPt.equals2D(p11)) return PolygonIntersectionAnalyzer.NO_INVALID_INTERSECTION
|
||||
let e00 = p00
|
||||
let e01 = p01
|
||||
if (intPt.equals2D(p00)) {
|
||||
e00 = PolygonIntersectionAnalyzer.prevCoordinateInRing(ss0, segIndex0)
|
||||
e01 = p01
|
||||
}
|
||||
let e10 = p10
|
||||
let e11 = p11
|
||||
if (intPt.equals2D(p10)) {
|
||||
e10 = PolygonIntersectionAnalyzer.prevCoordinateInRing(ss1, segIndex1)
|
||||
e11 = p11
|
||||
}
|
||||
const hasCrossing = PolygonNodeTopology.isCrossing(intPt, e00, e01, e10, e11)
|
||||
if (hasCrossing)
|
||||
return TopologyValidationError.SELF_INTERSECTION
|
||||
|
||||
if (isSameSegString && this._isInvertedRingValid)
|
||||
this.addSelfTouch(ss0, intPt, e00, e01, e10, e11)
|
||||
|
||||
const isDoubleTouch = this.addDoubleTouch(ss0, ss1, intPt)
|
||||
if (isDoubleTouch && !isSameSegString) {
|
||||
this._hasDoubleTouch = true
|
||||
this._doubleTouchLocation = intPt
|
||||
}
|
||||
return PolygonIntersectionAnalyzer.NO_INVALID_INTERSECTION
|
||||
}
|
||||
processIntersections(ss0, segIndex0, ss1, segIndex1) {
|
||||
const isSameSegString = ss0 === ss1
|
||||
const isSameSegment = isSameSegString && segIndex0 === segIndex1
|
||||
if (isSameSegment) return null
|
||||
const code = this.findInvalidIntersection(ss0, segIndex0, ss1, segIndex1)
|
||||
if (code !== PolygonIntersectionAnalyzer.NO_INVALID_INTERSECTION) {
|
||||
this._invalidCode = code
|
||||
this._invalidLocation = this._li.getIntersection(0)
|
||||
}
|
||||
}
|
||||
isDone() {
|
||||
return this.isInvalid() || this._hasDoubleTouch
|
||||
}
|
||||
addDoubleTouch(ss0, ss1, intPt) {
|
||||
return PolygonRing.addTouch(ss0.getData(), ss1.getData(), intPt)
|
||||
}
|
||||
isInvalid() {
|
||||
return this._invalidCode >= 0
|
||||
}
|
||||
getInvalidLocation() {
|
||||
return this._invalidLocation
|
||||
}
|
||||
getInvalidCode() {
|
||||
return this._invalidCode
|
||||
}
|
||||
get interfaces_() {
|
||||
return [SegmentIntersector]
|
||||
}
|
||||
}
|
||||
PolygonIntersectionAnalyzer.NO_INVALID_INTERSECTION = -1
|
||||
@ -1,196 +0,0 @@
|
||||
import ArrayDeque from '../../../../../java/util/ArrayDeque.js'
|
||||
import HashMap from '../../../../../java/util/HashMap.js'
|
||||
import PolygonNodeTopology from '../../algorithm/PolygonNodeTopology.js'
|
||||
import Orientation from '../../algorithm/Orientation.js'
|
||||
import ArrayList from '../../../../../java/util/ArrayList.js'
|
||||
export default class PolygonRing {
|
||||
constructor() {
|
||||
PolygonRing.constructor_.apply(this, arguments)
|
||||
}
|
||||
static constructor_() {
|
||||
this._id = null
|
||||
this._shell = null
|
||||
this._ring = null
|
||||
this._touchSetRoot = null
|
||||
this._touches = null
|
||||
this._selfNodes = null
|
||||
if (arguments.length === 1) {
|
||||
const ring = arguments[0]
|
||||
this._ring = ring
|
||||
this._id = -1
|
||||
this._shell = this
|
||||
} else if (arguments.length === 3) {
|
||||
const ring = arguments[0], index = arguments[1], shell = arguments[2]
|
||||
this._ring = ring
|
||||
this._id = index
|
||||
this._shell = shell
|
||||
}
|
||||
}
|
||||
static findInteriorSelfNode(polyRings) {
|
||||
for (const polyRing of polyRings) {
|
||||
const interiorSelfNode = polyRing.findInteriorSelfNode()
|
||||
if (interiorSelfNode !== null)
|
||||
return interiorSelfNode
|
||||
|
||||
}
|
||||
return null
|
||||
}
|
||||
static addTouch(ring0, ring1, pt) {
|
||||
if (ring0 === null || ring1 === null) return false
|
||||
if (!ring0.isSamePolygon(ring1)) return false
|
||||
if (!ring0.isOnlyTouch(ring1, pt)) return true
|
||||
if (!ring1.isOnlyTouch(ring0, pt)) return true
|
||||
ring0.addTouch(ring1, pt)
|
||||
ring1.addTouch(ring0, pt)
|
||||
return false
|
||||
}
|
||||
static isShell(polyRing) {
|
||||
if (polyRing === null) return true
|
||||
return polyRing.isShell()
|
||||
}
|
||||
static findHoleCycleLocation(polyRings) {
|
||||
for (const polyRing of polyRings)
|
||||
if (!polyRing.isInTouchSet()) {
|
||||
const holeCycleLoc = polyRing.findHoleCycleLocation()
|
||||
if (holeCycleLoc !== null) return holeCycleLoc
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
static init(root, touchStack) {
|
||||
for (const touch of root.getTouches()) {
|
||||
touch.getRing().setTouchSetRoot(root)
|
||||
touchStack.push(touch)
|
||||
}
|
||||
}
|
||||
scanForHoleCycle(currentTouch, root, touchStack) {
|
||||
const ring = currentTouch.getRing()
|
||||
const currentPt = currentTouch.getCoordinate()
|
||||
for (const touch of ring.getTouches()) {
|
||||
if (currentPt.equals2D(touch.getCoordinate())) continue
|
||||
const touchRing = touch.getRing()
|
||||
if (touchRing.getTouchSetRoot() === root) return touch.getCoordinate()
|
||||
touchRing.setTouchSetRoot(root)
|
||||
touchStack.push(touch)
|
||||
}
|
||||
return null
|
||||
}
|
||||
findInteriorSelfNode() {
|
||||
if (this._selfNodes === null) return null
|
||||
const isCCW = Orientation.isCCW(this._ring.getCoordinates())
|
||||
const isInteriorOnRight = this.isShell() ^ isCCW
|
||||
for (const selfNode of this._selfNodes)
|
||||
if (!selfNode.isExterior(isInteriorOnRight))
|
||||
return selfNode.getCoordinate()
|
||||
|
||||
|
||||
return null
|
||||
}
|
||||
addTouch(ring, pt) {
|
||||
if (this._touches === null)
|
||||
this._touches = new HashMap()
|
||||
|
||||
const touch = this._touches.get(ring._id)
|
||||
if (touch === null)
|
||||
this._touches.put(ring._id, new PolygonRingTouch(ring, pt))
|
||||
|
||||
|
||||
}
|
||||
addSelfTouch(origin, e00, e01, e10, e11) {
|
||||
if (this._selfNodes === null)
|
||||
this._selfNodes = new ArrayList()
|
||||
|
||||
this._selfNodes.add(new PolygonRingSelfNode(origin, e00, e01, e10, e11))
|
||||
}
|
||||
isOnlyTouch(ring, pt) {
|
||||
if (this._touches === null) return true
|
||||
const touch = this._touches.get(ring._id)
|
||||
if (touch === null) return true
|
||||
return touch.isAtLocation(pt)
|
||||
}
|
||||
getTouches() {
|
||||
return this._touches.values()
|
||||
}
|
||||
isShell() {
|
||||
return this._shell === this
|
||||
}
|
||||
hasTouches() {
|
||||
return this._touches !== null && !this._touches.isEmpty()
|
||||
}
|
||||
setTouchSetRoot(ring) {
|
||||
this._touchSetRoot = ring
|
||||
}
|
||||
findHoleCycleLocation() {
|
||||
if (this.isInTouchSet()) return null
|
||||
const root = this
|
||||
root.setTouchSetRoot(root)
|
||||
if (!this.hasTouches()) return null
|
||||
const touchStack = new ArrayDeque()
|
||||
PolygonRing.init(root, touchStack)
|
||||
while (!touchStack.isEmpty()) {
|
||||
const touch = touchStack.pop()
|
||||
const holeCyclePt = this.scanForHoleCycle(touch, root, touchStack)
|
||||
if (holeCyclePt !== null)
|
||||
return holeCyclePt
|
||||
|
||||
}
|
||||
return null
|
||||
}
|
||||
toString() {
|
||||
return this._ring.toString()
|
||||
}
|
||||
isInTouchSet() {
|
||||
return this._touchSetRoot !== null
|
||||
}
|
||||
isSamePolygon(ring) {
|
||||
return this._shell === ring._shell
|
||||
}
|
||||
getTouchSetRoot() {
|
||||
return this._touchSetRoot
|
||||
}
|
||||
}
|
||||
class PolygonRingTouch {
|
||||
constructor() {
|
||||
PolygonRingTouch.constructor_.apply(this, arguments)
|
||||
}
|
||||
static constructor_() {
|
||||
this._ring = null
|
||||
this._touchPt = null
|
||||
const ring = arguments[0], pt = arguments[1]
|
||||
this._ring = ring
|
||||
this._touchPt = pt
|
||||
}
|
||||
getCoordinate() {
|
||||
return this._touchPt
|
||||
}
|
||||
getRing() {
|
||||
return this._ring
|
||||
}
|
||||
isAtLocation(pt) {
|
||||
return this._touchPt.equals2D(pt)
|
||||
}
|
||||
}
|
||||
class PolygonRingSelfNode {
|
||||
constructor() {
|
||||
PolygonRingSelfNode.constructor_.apply(this, arguments)
|
||||
}
|
||||
static constructor_() {
|
||||
this._nodePt = null
|
||||
this._e00 = null
|
||||
this._e01 = null
|
||||
this._e10 = null
|
||||
const nodePt = arguments[0], e00 = arguments[1], e01 = arguments[2], e10 = arguments[3], e11 = arguments[4]
|
||||
this._nodePt = nodePt
|
||||
this._e00 = e00
|
||||
this._e01 = e01
|
||||
this._e10 = e10
|
||||
}
|
||||
getCoordinate() {
|
||||
return this._nodePt
|
||||
}
|
||||
isExterior(isInteriorOnRight) {
|
||||
const isInteriorSeg = PolygonNodeTopology.isInteriorSegment(this._nodePt, this._e00, this._e01, this._e10)
|
||||
const isExterior = isInteriorOnRight ? !isInteriorSeg : isInteriorSeg
|
||||
return isExterior
|
||||
}
|
||||
}
|
||||
@ -1,205 +0,0 @@
|
||||
import BasicSegmentString from '../../noding/BasicSegmentString.js'
|
||||
import Location from '../../geom/Location.js'
|
||||
import PolygonIntersectionAnalyzer from './PolygonIntersectionAnalyzer.js'
|
||||
import PolygonNodeTopology from '../../algorithm/PolygonNodeTopology.js'
|
||||
import IllegalArgumentException from '../../../../../java/lang/IllegalArgumentException.js'
|
||||
import MCIndexNoder from '../../noding/MCIndexNoder.js'
|
||||
import PointLocation from '../../algorithm/PointLocation.js'
|
||||
import LinearRing from '../../geom/LinearRing.js'
|
||||
import Orientation from '../../algorithm/Orientation.js'
|
||||
import CoordinateArrays from '../../geom/CoordinateArrays.js'
|
||||
import ArrayList from '../../../../../java/util/ArrayList.js'
|
||||
import PolygonRing from './PolygonRing.js'
|
||||
import RobustLineIntersector from '../../algorithm/RobustLineIntersector.js'
|
||||
export default class PolygonTopologyAnalyzer {
|
||||
constructor() {
|
||||
PolygonTopologyAnalyzer.constructor_.apply(this, arguments)
|
||||
}
|
||||
static constructor_() {
|
||||
this._isInvertedRingValid = null
|
||||
this._intFinder = null
|
||||
this._polyRings = null
|
||||
this._disconnectionPt = null
|
||||
const geom = arguments[0], isInvertedRingValid = arguments[1]
|
||||
this._isInvertedRingValid = isInvertedRingValid
|
||||
this.analyze(geom)
|
||||
}
|
||||
static ringIndexPrev(ringPts, index) {
|
||||
if (index === 0) return ringPts.length - 2
|
||||
return index - 1
|
||||
}
|
||||
static findNonEqualVertex(ring, p) {
|
||||
let i = 1
|
||||
let next = ring.getCoordinateN(i)
|
||||
while (next.equals2D(p) && i < ring.getNumPoints() - 1) {
|
||||
i += 1
|
||||
next = ring.getCoordinateN(i)
|
||||
}
|
||||
return next
|
||||
}
|
||||
static ringIndexNext(ringPts, index) {
|
||||
if (index >= ringPts.length - 2) return 0
|
||||
return index + 1
|
||||
}
|
||||
static createSegmentStrings(geom, isInvertedRingValid) {
|
||||
const segStrings = new ArrayList()
|
||||
if (geom instanceof LinearRing) {
|
||||
const ring = geom
|
||||
segStrings.add(PolygonTopologyAnalyzer.createSegString(ring, null))
|
||||
return segStrings
|
||||
}
|
||||
for (let i = 0; i < geom.getNumGeometries(); i++) {
|
||||
const poly = geom.getGeometryN(i)
|
||||
if (poly.isEmpty()) continue
|
||||
const hasHoles = poly.getNumInteriorRing() > 0
|
||||
let shellRing = null
|
||||
if (hasHoles || isInvertedRingValid)
|
||||
shellRing = new PolygonRing(poly.getExteriorRing())
|
||||
|
||||
segStrings.add(PolygonTopologyAnalyzer.createSegString(poly.getExteriorRing(), shellRing))
|
||||
for (let j = 0; j < poly.getNumInteriorRing(); j++) {
|
||||
const hole = poly.getInteriorRingN(j)
|
||||
if (hole.isEmpty()) continue
|
||||
const holeRing = new PolygonRing(hole, j, shellRing)
|
||||
segStrings.add(PolygonTopologyAnalyzer.createSegString(hole, holeRing))
|
||||
}
|
||||
}
|
||||
return segStrings
|
||||
}
|
||||
static isIncidentSegmentInRing(p0, p1, ringPts) {
|
||||
const index = PolygonTopologyAnalyzer.intersectingSegIndex(ringPts, p0)
|
||||
if (index < 0)
|
||||
throw new IllegalArgumentException('Segment vertex does not intersect ring')
|
||||
|
||||
let rPrev = PolygonTopologyAnalyzer.findRingVertexPrev(ringPts, index, p0)
|
||||
let rNext = PolygonTopologyAnalyzer.findRingVertexNext(ringPts, index, p0)
|
||||
const isInteriorOnRight = !Orientation.isCCW(ringPts)
|
||||
if (!isInteriorOnRight) {
|
||||
const temp = rPrev
|
||||
rPrev = rNext
|
||||
rNext = temp
|
||||
}
|
||||
return PolygonNodeTopology.isInteriorSegment(p0, rPrev, rNext, p1)
|
||||
}
|
||||
static intersectingSegIndex(ringPts, pt) {
|
||||
const li = new RobustLineIntersector()
|
||||
for (let i = 0; i < ringPts.length - 1; i++) {
|
||||
li.computeIntersection(pt, ringPts[i], ringPts[i + 1])
|
||||
if (li.hasIntersection()) {
|
||||
if (pt.equals2D(ringPts[i + 1]))
|
||||
return i + 1
|
||||
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
static findRingVertexNext(ringPts, index, node) {
|
||||
let iNext = index + 1
|
||||
let next = ringPts[iNext]
|
||||
while (node.equals2D(next)) {
|
||||
iNext = PolygonTopologyAnalyzer.ringIndexNext(ringPts, iNext)
|
||||
next = ringPts[iNext]
|
||||
}
|
||||
return next
|
||||
}
|
||||
static getPolygonRings(segStrings) {
|
||||
let polyRings = null
|
||||
for (const ss of segStrings) {
|
||||
const polyRing = ss.getData()
|
||||
if (polyRing !== null) {
|
||||
if (polyRings === null)
|
||||
polyRings = new ArrayList()
|
||||
|
||||
polyRings.add(polyRing)
|
||||
}
|
||||
}
|
||||
return polyRings
|
||||
}
|
||||
static createSegString(ring, polyRing) {
|
||||
let pts = ring.getCoordinates()
|
||||
if (CoordinateArrays.hasRepeatedPoints(pts))
|
||||
pts = CoordinateArrays.removeRepeatedPoints(pts)
|
||||
|
||||
const ss = new BasicSegmentString(pts, polyRing)
|
||||
return ss
|
||||
}
|
||||
static findSelfIntersection(ring) {
|
||||
const ata = new PolygonTopologyAnalyzer(ring, false)
|
||||
if (ata.hasInvalidIntersection()) return ata.getInvalidLocation()
|
||||
return null
|
||||
}
|
||||
static isRingNested(test, target) {
|
||||
const p0 = test.getCoordinateN(0)
|
||||
const targetPts = target.getCoordinates()
|
||||
const loc = PointLocation.locateInRing(p0, targetPts)
|
||||
if (loc === Location.EXTERIOR) return false
|
||||
if (loc === Location.INTERIOR) return true
|
||||
const p1 = PolygonTopologyAnalyzer.findNonEqualVertex(test, p0)
|
||||
return PolygonTopologyAnalyzer.isIncidentSegmentInRing(p0, p1, targetPts)
|
||||
}
|
||||
static findRingVertexPrev(ringPts, index, node) {
|
||||
let iPrev = index
|
||||
let prev = ringPts[iPrev]
|
||||
while (node.equals2D(prev)) {
|
||||
iPrev = PolygonTopologyAnalyzer.ringIndexPrev(ringPts, iPrev)
|
||||
prev = ringPts[iPrev]
|
||||
}
|
||||
return prev
|
||||
}
|
||||
analyze(geom) {
|
||||
if (geom.isEmpty()) return null
|
||||
const segStrings = PolygonTopologyAnalyzer.createSegmentStrings(geom, this._isInvertedRingValid)
|
||||
this._polyRings = PolygonTopologyAnalyzer.getPolygonRings(segStrings)
|
||||
this._intFinder = this.analyzeIntersections(segStrings)
|
||||
if (this._intFinder.hasDoubleTouch()) {
|
||||
this._disconnectionPt = this._intFinder.getDoubleTouchLocation()
|
||||
return null
|
||||
}
|
||||
}
|
||||
checkInteriorDisconnectedByHoleCycle() {
|
||||
if (this._polyRings !== null)
|
||||
this._disconnectionPt = PolygonRing.findHoleCycleLocation(this._polyRings)
|
||||
|
||||
}
|
||||
getDisconnectionLocation() {
|
||||
return this._disconnectionPt
|
||||
}
|
||||
hasInvalidIntersection() {
|
||||
return this._intFinder.isInvalid()
|
||||
}
|
||||
analyzeIntersections(segStrings) {
|
||||
const segInt = new PolygonIntersectionAnalyzer(this._isInvertedRingValid)
|
||||
const noder = new MCIndexNoder()
|
||||
noder.setSegmentIntersector(segInt)
|
||||
noder.computeNodes(segStrings)
|
||||
return segInt
|
||||
}
|
||||
isInteriorDisconnected() {
|
||||
if (this._disconnectionPt !== null)
|
||||
return true
|
||||
|
||||
if (this._isInvertedRingValid) {
|
||||
this.checkInteriorDisconnectedBySelfTouch()
|
||||
if (this._disconnectionPt !== null)
|
||||
return true
|
||||
|
||||
}
|
||||
this.checkInteriorDisconnectedByHoleCycle()
|
||||
if (this._disconnectionPt !== null)
|
||||
return true
|
||||
|
||||
return false
|
||||
}
|
||||
checkInteriorDisconnectedBySelfTouch() {
|
||||
if (this._polyRings !== null)
|
||||
this._disconnectionPt = PolygonRing.findInteriorSelfNode(this._polyRings)
|
||||
|
||||
}
|
||||
getInvalidLocation() {
|
||||
return this._intFinder.getInvalidLocation()
|
||||
}
|
||||
getInvalidCode() {
|
||||
return this._intFinder.getInvalidCode()
|
||||
}
|
||||
}
|
||||
@ -1,30 +0,0 @@
|
||||
import GeometryTransformer from '../geom/util/GeometryTransformer.js'
|
||||
export default class PointwisePrecisionReducerTransformer extends GeometryTransformer {
|
||||
constructor() {
|
||||
super()
|
||||
PointwisePrecisionReducerTransformer.constructor_.apply(this, arguments)
|
||||
}
|
||||
static constructor_() {
|
||||
this._targetPM = null
|
||||
const targetPM = arguments[0]
|
||||
this._targetPM = targetPM
|
||||
}
|
||||
static reduce(geom, targetPM) {
|
||||
const trans = new PointwisePrecisionReducerTransformer(targetPM)
|
||||
return trans.transform(geom)
|
||||
}
|
||||
transformCoordinates(coordinates, parent) {
|
||||
if (coordinates.size() === 0) return null
|
||||
const coordsReduce = this.reducePointwise(coordinates)
|
||||
return this._factory.getCoordinateSequenceFactory().create(coordsReduce)
|
||||
}
|
||||
reducePointwise(coordinates) {
|
||||
const coordReduce = new Array(coordinates.size()).fill(null)
|
||||
for (let i = 0; i < coordinates.size(); i++) {
|
||||
const coord = coordinates.getCoordinate(i).copy()
|
||||
this._targetPM.makePrecise(coord)
|
||||
coordReduce[i] = coord
|
||||
}
|
||||
return coordReduce
|
||||
}
|
||||
}
|
||||
@ -1,65 +0,0 @@
|
||||
import LineString from '../geom/LineString.js'
|
||||
import CoordinateList from '../geom/CoordinateList.js'
|
||||
import GeometryTransformer from '../geom/util/GeometryTransformer.js'
|
||||
import LinearRing from '../geom/LinearRing.js'
|
||||
import PrecisionReducer from '../operation/overlayng/PrecisionReducer.js'
|
||||
export default class PrecisionReducerTransformer extends GeometryTransformer {
|
||||
constructor() {
|
||||
super()
|
||||
PrecisionReducerTransformer.constructor_.apply(this, arguments)
|
||||
}
|
||||
static constructor_() {
|
||||
this._targetPM = null
|
||||
this._isRemoveCollapsed = false
|
||||
const targetPM = arguments[0], isRemoveCollapsed = arguments[1]
|
||||
this._targetPM = targetPM
|
||||
this._isRemoveCollapsed = isRemoveCollapsed
|
||||
}
|
||||
static reduce(geom, targetPM, isRemoveCollapsed) {
|
||||
const trans = new PrecisionReducerTransformer(targetPM, isRemoveCollapsed)
|
||||
return trans.transform(geom)
|
||||
}
|
||||
transformPolygon(geom, parent) {
|
||||
return this.reduceArea(geom)
|
||||
}
|
||||
reduceCompress(coordinates) {
|
||||
const noRepeatCoordList = new CoordinateList()
|
||||
for (let i = 0; i < coordinates.size(); i++) {
|
||||
const coord = coordinates.getCoordinate(i).copy()
|
||||
this._targetPM.makePrecise(coord)
|
||||
noRepeatCoordList.add(coord, false)
|
||||
}
|
||||
const noRepeatCoords = noRepeatCoordList.toCoordinateArray()
|
||||
return noRepeatCoords
|
||||
}
|
||||
transformCoordinates(coordinates, parent) {
|
||||
if (coordinates.size() === 0) return null
|
||||
let coordsReduce = this.reduceCompress(coordinates)
|
||||
let minSize = 0
|
||||
if (parent instanceof LineString) minSize = 2
|
||||
if (parent instanceof LinearRing) minSize = LinearRing.MINIMUM_VALID_SIZE
|
||||
if (coordsReduce.length < minSize) {
|
||||
if (this._isRemoveCollapsed)
|
||||
return null
|
||||
|
||||
coordsReduce = this.extend(coordsReduce, minSize)
|
||||
}
|
||||
return this._factory.getCoordinateSequenceFactory().create(coordsReduce)
|
||||
}
|
||||
extend(coords, minLength) {
|
||||
if (coords.length >= minLength) return coords
|
||||
const exCoords = new Array(minLength).fill(null)
|
||||
for (let i = 0; i < exCoords.length; i++) {
|
||||
const iSrc = i < coords.length ? i : coords.length - 1
|
||||
exCoords[i] = coords[iSrc].copy()
|
||||
}
|
||||
return exCoords
|
||||
}
|
||||
transformMultiPolygon(geom, parent) {
|
||||
return this.reduceArea(geom)
|
||||
}
|
||||
reduceArea(geom) {
|
||||
const reduced = PrecisionReducer.reducePrecision(geom, this._targetPM)
|
||||
return reduced
|
||||
}
|
||||
}
|
||||
@ -1,250 +0,0 @@
|
||||
import LineString from '../geom/LineString.js'
|
||||
import CoordinateList from '../geom/CoordinateList.js'
|
||||
import Geometry from '../geom/Geometry.js'
|
||||
import Coordinate from '../geom/Coordinate.js'
|
||||
import IllegalArgumentException from '../../../../java/lang/IllegalArgumentException.js'
|
||||
import Polygon from '../geom/Polygon.js'
|
||||
import GeometryMapper from '../geom/util/GeometryMapper.js'
|
||||
import Angle from '../algorithm/Angle.js'
|
||||
export default class CubicBezierCurve {
|
||||
constructor() {
|
||||
CubicBezierCurve.constructor_.apply(this, arguments)
|
||||
}
|
||||
static constructor_() {
|
||||
this._minSegmentLength = 0.0
|
||||
this._numVerticesPerSegment = 16
|
||||
this._inputGeom = null
|
||||
this._alpha = -1
|
||||
this._skew = 0
|
||||
this._controlPoints = null
|
||||
this._geomFactory = null
|
||||
this._bezierCurvePts = null
|
||||
this._interpolationParam = null
|
||||
this._controlPointIndex = 0
|
||||
if (arguments.length === 2) {
|
||||
if (arguments[0] instanceof Geometry && arguments[1] instanceof Geometry) {
|
||||
const geom = arguments[0], controlPoints = arguments[1]
|
||||
this._inputGeom = geom
|
||||
this._geomFactory = geom.getFactory()
|
||||
this._controlPoints = controlPoints
|
||||
} else if (arguments[0] instanceof Geometry && typeof arguments[1] === 'number') {
|
||||
let geom = arguments[0], alpha = arguments[1]
|
||||
this._inputGeom = geom
|
||||
this._geomFactory = geom.getFactory()
|
||||
if (alpha < 0.0) alpha = 0
|
||||
this._alpha = alpha
|
||||
}
|
||||
} else if (arguments.length === 3) {
|
||||
let geom = arguments[0], alpha = arguments[1], skew = arguments[2]
|
||||
this._inputGeom = geom
|
||||
this._geomFactory = geom.getFactory()
|
||||
if (alpha < 0.0) alpha = 0
|
||||
this._alpha = alpha
|
||||
this._skew = skew
|
||||
}
|
||||
}
|
||||
static computeIterpolationParameters(n) {
|
||||
const param = Array(n).fill().map(() => Array(4))
|
||||
for (let i = 0; i < n; i++) {
|
||||
const t = i / (n - 1)
|
||||
const tc = 1.0 - t
|
||||
param[i][0] = tc * tc * tc
|
||||
param[i][1] = 3.0 * tc * tc * t
|
||||
param[i][2] = 3.0 * tc * t * t
|
||||
param[i][3] = t * t * t
|
||||
}
|
||||
return param
|
||||
}
|
||||
static mirrorControlPoint(c, p0, p1) {
|
||||
const vlinex = p1.x - p0.x
|
||||
const vliney = p1.y - p0.y
|
||||
const vrotx = -vliney
|
||||
const vroty = vlinex
|
||||
const midx = (p0.x + p1.x) / 2
|
||||
const midy = (p0.y + p1.y) / 2
|
||||
return CubicBezierCurve.reflectPointInLine(c, new Coordinate(midx, midy), new Coordinate(midx + vrotx, midy + vroty))
|
||||
}
|
||||
static bezierCurve() {
|
||||
if (arguments.length === 2) {
|
||||
if (arguments[0] instanceof Geometry && arguments[1] instanceof Geometry) {
|
||||
const geom = arguments[0], controlPoints = arguments[1]
|
||||
const curve = new CubicBezierCurve(geom, controlPoints)
|
||||
return curve.getResult()
|
||||
} else if (arguments[0] instanceof Geometry && typeof arguments[1] === 'number') {
|
||||
const geom = arguments[0], alpha = arguments[1]
|
||||
const curve = new CubicBezierCurve(geom, alpha)
|
||||
return curve.getResult()
|
||||
}
|
||||
} else if (arguments.length === 3) {
|
||||
const geom = arguments[0], alpha = arguments[1], skew = arguments[2]
|
||||
const curve = new CubicBezierCurve(geom, alpha, skew)
|
||||
return curve.getResult()
|
||||
}
|
||||
}
|
||||
static reflectPointInLine(p, p0, p1) {
|
||||
const vx = p1.x - p0.x
|
||||
const vy = p1.y - p0.y
|
||||
const x = p0.x - p.x
|
||||
const y = p0.y - p.y
|
||||
const r = 1 / (vx * vx + vy * vy)
|
||||
const rx = p.x + 2 * (x - x * vx * vx * r - y * vx * vy * r)
|
||||
const ry = p.y + 2 * (y - y * vy * vy * r - x * vx * vy * r)
|
||||
return new Coordinate(rx, ry)
|
||||
}
|
||||
static aimedControlPoint(c, p1, p0) {
|
||||
const len = p1.distance(c)
|
||||
const ang = Angle.angle(p0, p1)
|
||||
return Angle.project(p0, ang, len)
|
||||
}
|
||||
getResult() {
|
||||
this._bezierCurvePts = new Array(this._numVerticesPerSegment).fill(null)
|
||||
this._interpolationParam = CubicBezierCurve.computeIterpolationParameters(this._numVerticesPerSegment)
|
||||
return GeometryMapper.flatMap(this._inputGeom, 1, new (class {
|
||||
get interfaces_() {
|
||||
return [MapOp]
|
||||
}
|
||||
map(geom) {
|
||||
if (geom instanceof LineString)
|
||||
return this.bezierLine(geom)
|
||||
|
||||
if (geom instanceof Polygon)
|
||||
return this.bezierPolygon(geom)
|
||||
|
||||
return geom.copy()
|
||||
}
|
||||
})())
|
||||
}
|
||||
bezierPolygon(poly) {
|
||||
const shell = this.bezierRing(poly.getExteriorRing())
|
||||
let holes = null
|
||||
if (poly.getNumInteriorRing() > 0) {
|
||||
holes = new Array(poly.getNumInteriorRing()).fill(null)
|
||||
for (let i = 0; i < poly.getNumInteriorRing(); i++)
|
||||
holes[i] = this.bezierRing(poly.getInteriorRingN(i))
|
||||
|
||||
}
|
||||
return this._geomFactory.createPolygon(shell, holes)
|
||||
}
|
||||
controlPoints() {
|
||||
if (arguments.length === 2) {
|
||||
const coords = arguments[0], isRing = arguments[1]
|
||||
if (this._controlPoints !== null) {
|
||||
if (this._controlPointIndex >= this._controlPoints.getNumGeometries())
|
||||
throw new IllegalArgumentException('Too few control point elements')
|
||||
|
||||
const ctrlPtsGeom = this._controlPoints.getGeometryN(this._controlPointIndex++)
|
||||
const ctrlPts = ctrlPtsGeom.getCoordinates()
|
||||
const expectedNum1 = 2 * coords.length - 2
|
||||
const expectedNum2 = isRing ? coords.length - 1 : coords.length
|
||||
if (expectedNum1 !== ctrlPts.length && expectedNum2 !== ctrlPts.length)
|
||||
throw new IllegalArgumentException(String.format('Wrong number of control points for element %d - expected %d or %d, found %d', this._controlPointIndex - 1, expectedNum1, expectedNum2, ctrlPts.length))
|
||||
|
||||
return ctrlPts
|
||||
}
|
||||
return this.controlPoints(coords, isRing, this._alpha, this._skew)
|
||||
} else if (arguments.length === 4) {
|
||||
const coords = arguments[0], isRing = arguments[1], alpha = arguments[2], skew = arguments[3]
|
||||
let N = coords.length
|
||||
let start = 1
|
||||
let end = N - 1
|
||||
if (isRing) {
|
||||
N = coords.length - 1
|
||||
start = 0
|
||||
end = N
|
||||
}
|
||||
const nControl = 2 * coords.length - 2
|
||||
const ctrl = new Array(nControl).fill(null)
|
||||
for (let i = start; i < end; i++) {
|
||||
const iprev = i === 0 ? N - 1 : i - 1
|
||||
const v0 = coords[iprev]
|
||||
const v1 = coords[i]
|
||||
const v2 = coords[i + 1]
|
||||
const interiorAng = Angle.angleBetweenOriented(v0, v1, v2)
|
||||
const orient = Math.signum(interiorAng)
|
||||
const angBisect = Angle.bisector(v0, v1, v2)
|
||||
const ang0 = angBisect - orient * Angle.PI_OVER_2
|
||||
const ang1 = angBisect + orient * Angle.PI_OVER_2
|
||||
const dist0 = v1.distance(v0)
|
||||
const dist1 = v1.distance(v2)
|
||||
const lenBase = Math.min(dist0, dist1)
|
||||
const intAngAbs = Math.abs(interiorAng)
|
||||
const sharpnessFactor = intAngAbs >= Angle.PI_OVER_2 ? 1 : intAngAbs / Angle.PI_OVER_2
|
||||
const len = alpha * CubicBezierCurve.CIRCLE_LEN_FACTOR * sharpnessFactor * lenBase
|
||||
let stretch0 = 1
|
||||
let stretch1 = 1
|
||||
if (skew !== 0) {
|
||||
const stretch = Math.abs(dist0 - dist1) / Math.max(dist0, dist1)
|
||||
let skewIndex = dist0 > dist1 ? 0 : 1
|
||||
if (skew < 0) skewIndex = 1 - skewIndex
|
||||
if (skewIndex === 0)
|
||||
stretch0 += Math.abs(skew) * stretch
|
||||
else
|
||||
stretch1 += Math.abs(skew) * stretch
|
||||
|
||||
}
|
||||
const ctl0 = Angle.project(v1, ang0, stretch0 * len)
|
||||
const ctl1 = Angle.project(v1, ang1, stretch1 * len)
|
||||
const index = 2 * i - 1
|
||||
const i0 = index < 0 ? nControl - 1 : index
|
||||
ctrl[i0] = ctl0
|
||||
ctrl[index + 1] = ctl1
|
||||
}
|
||||
if (!isRing)
|
||||
this.setLineEndControlPoints(coords, ctrl)
|
||||
|
||||
return ctrl
|
||||
}
|
||||
}
|
||||
bezierCurve(coords, isRing) {
|
||||
const control = this.controlPoints(coords, isRing)
|
||||
const curvePts = new CoordinateList()
|
||||
for (let i = 0; i < coords.length - 1; i++) {
|
||||
const ctrlIndex = 2 * i
|
||||
this.addCurve(coords[i], coords[i + 1], control[ctrlIndex], control[ctrlIndex + 1], curvePts)
|
||||
}
|
||||
return curvePts
|
||||
}
|
||||
setLineEndControlPoints(coords, ctrl) {
|
||||
const N = ctrl.length
|
||||
ctrl[0] = CubicBezierCurve.mirrorControlPoint(ctrl[1], coords[1], coords[0])
|
||||
ctrl[N - 1] = CubicBezierCurve.mirrorControlPoint(ctrl[N - 2], coords[coords.length - 1], coords[coords.length - 2])
|
||||
}
|
||||
bezierRing(ring) {
|
||||
const coords = ring.getCoordinates()
|
||||
const curvePts = this.bezierCurve(coords, true)
|
||||
curvePts.closeRing()
|
||||
return this._geomFactory.createLinearRing(curvePts.toCoordinateArray())
|
||||
}
|
||||
cubicBezier(p0, p1, ctrl1, ctrl2, param, curve) {
|
||||
const n = curve.length
|
||||
curve[0] = new Coordinate(p0)
|
||||
curve[n - 1] = new Coordinate(p1)
|
||||
for (let i = 1; i < n - 1; i++) {
|
||||
const c = new Coordinate()
|
||||
const sum = param[i][0] + param[i][1] + param[i][2] + param[i][3]
|
||||
c.x = param[i][0] * p0.x + param[i][1] * ctrl1.x + param[i][2] * ctrl2.x + param[i][3] * p1.x
|
||||
c.x /= sum
|
||||
c.y = param[i][0] * p0.y + param[i][1] * ctrl1.y + param[i][2] * ctrl2.y + param[i][3] * p1.y
|
||||
c.y /= sum
|
||||
curve[i] = c
|
||||
}
|
||||
}
|
||||
addCurve(p0, p1, ctrl0, crtl1, curvePts) {
|
||||
const len = p0.distance(p1)
|
||||
if (len < this._minSegmentLength) {
|
||||
curvePts.add(new Coordinate(p0))
|
||||
} else {
|
||||
this.cubicBezier(p0, p1, ctrl0, crtl1, this._interpolationParam, this._bezierCurvePts)
|
||||
for (let i = 0; i < this._bezierCurvePts.length - 1; i++)
|
||||
curvePts.add(this._bezierCurvePts[i], false)
|
||||
|
||||
}
|
||||
}
|
||||
bezierLine(ls) {
|
||||
const coords = ls.getCoordinates()
|
||||
const curvePts = this.bezierCurve(coords, false)
|
||||
curvePts.add(coords[coords.length - 1].copy(), false)
|
||||
return this._geomFactory.createLineString(curvePts.toCoordinateArray())
|
||||
}
|
||||
}
|
||||
CubicBezierCurve.CIRCLE_LEN_FACTOR = 3.0 / 8.0
|
||||
@ -411,7 +411,7 @@ class TriangleCoordinatesVisitor {
|
||||
checkTriangleSize(pts) {
|
||||
let loc = ''
|
||||
if (pts.length >= 2) loc = WKTWriter.toLineString(pts[0], pts[1]); else
|
||||
if (pts.length >= 1) loc = WKTWriter.toPoint(pts[0])
|
||||
if (pts.length >= 1) loc = WKTWriter.toPoint(pts[0])
|
||||
|
||||
}
|
||||
visit(triEdges) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user