CoverageRing.java
/*
* Copyright (c) 2022 Martin Davis.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* and Eclipse Distribution License v. 1.0 which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
* and the Eclipse Distribution License is available at
*
* http://www.eclipse.org/org/documents/edl-v10.php.
*/
package org.locationtech.jts.coverage;
import java.util.ArrayList;
import java.util.List;
import org.locationtech.jts.algorithm.Orientation;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.CoordinateArrays;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.LinearRing;
import org.locationtech.jts.geom.Polygon;
import org.locationtech.jts.geom.util.PolygonExtracter;
import org.locationtech.jts.noding.BasicSegmentString;
class CoverageRing extends BasicSegmentString {
public static List<CoverageRing> createRings(Geometry geom)
{
List<Polygon> polygons = PolygonExtracter.getPolygons(geom);
return createRings(polygons);
}
public static List<CoverageRing> createRings(List<Polygon> polygons) {
List<CoverageRing> rings = new ArrayList<CoverageRing>();
for (Polygon poly : polygons) {
createRings(poly, rings);
}
return rings;
}
private static void createRings(Polygon poly, List<CoverageRing> rings) {
if (poly.isEmpty())
return;
addRing(poly.getExteriorRing(), true, rings);
for (int i = 0; i < poly.getNumInteriorRing(); i++) {
addRing( poly.getInteriorRingN(i), false, rings);
}
}
private static void addRing(LinearRing ring, boolean isShell, List<CoverageRing> rings) {
if (ring.isEmpty())
return;
rings.add( createRing(ring, isShell));
}
private static CoverageRing createRing(LinearRing ring, boolean isShell) {
Coordinate[] pts = ring.getCoordinates();
if (CoordinateArrays.hasRepeatedOrInvalidPoints(pts)) {
pts = CoordinateArrays.removeRepeatedOrInvalidPoints(pts);
}
boolean isCCW = Orientation.isCCW(pts);
boolean isInteriorOnRight = isShell ? ! isCCW : isCCW;
return new CoverageRing(pts, isInteriorOnRight);
}
/**
* Tests if all rings have known status (matched or invalid)
* for all segments.
*
* @param rings a list of rings
* @return true if all ring segments have known status
*/
public static boolean isKnown(List<CoverageRing> rings) {
for (CoverageRing ring : rings) {
if (! ring.isKnown())
return false;
}
return true;
}
private boolean isInteriorOnRight;
private boolean[] isInvalid;
private boolean[] isMatched;
private CoverageRing(Coordinate[] pts, boolean isInteriorOnRight) {
super(pts, null);
this.isInteriorOnRight = isInteriorOnRight;
isInvalid = new boolean[size() - 1];
isMatched = new boolean[size() - 1];
}
public Envelope getEnvelope(int start, int end) {
Envelope env = new Envelope();
for (int i = start; i < end; i++) {
env.expandToInclude(getCoordinate(i));
}
return env;
}
/**
* Reports if the ring has canonical orientation,
* with the polygon interior on the right (shell is CW).
*
* @return true if the polygon interior is on the right
*/
public boolean isInteriorOnRight() {
return isInteriorOnRight;
}
/**
* Marks a segment as invalid.
*
* @param i the segment index
*/
public void markInvalid(int i) {
isInvalid[i] = true;
}
/**
* Marks a segment as valid.
*
* @param i the segment index
*/
public void markMatched(int i) {
//if (isInvalid[i])
// throw new IllegalStateException("Setting invalid edge to matched");
isMatched[i] = true;
}
/**
* Tests if all segments in the ring have known status
* (matched or invalid).
*
* @return true if all segments have known status
*/
public boolean isKnown() {
for (int i = 0; i < isMatched.length; i++) {
if (! (isMatched[i] && isInvalid[i]))
return false;
}
return true;
}
/**
* Tests if a segment is marked invalid.
*
* @param index the segment index
* @return true if the segment is invalid
*/
public boolean isInvalid(int index) {
return isInvalid[index];
}
/**
* Tests whether all segments are invalid.
*
* @return true if all segments are invalid
*/
public boolean isInvalid() {
for (int i = 0; i < isInvalid.length; i++) {
if (! isInvalid[i])
return false;
}
return true;
}
/**
* Tests whether any segment is invalid.
*
* @return true if some segment is invalid
*/
public boolean hasInvalid() {
for (int i = 0; i < isInvalid.length; i++) {
if (isInvalid[i])
return true;
}
return false;
}
/**
* Tests whether the matched/invalid state of a ring segment is known.
*
* @param i the index of the ring segment
* @return true if the segment state is known
*/
public boolean isKnown(int i) {
return isMatched[i] || isInvalid[i];
}
/**
* Finds the previous vertex in the ring which is distinct from a given coordinate value.
*
* @param index the index to start the search
* @param pt a coordinate value (which may not be a ring vertex)
* @return the previous distinct vertex in the ring
*/
public Coordinate findVertexPrev(int index, Coordinate pt) {
int iPrev = index;
Coordinate prev = getCoordinate(iPrev);
while (pt.equals2D(prev)) {
iPrev = prev(iPrev);
prev = getCoordinate(iPrev);
}
return prev;
}
/**
* Finds the next vertex in the ring which is distinct from a given coordinate value.
*
* @param index the index to start the search
* @param pt a coordinate value (which may not be a ring vertex)
* @return the next distinct vertex in the ring
*/
public Coordinate findVertexNext(int index, Coordinate pt) {
//-- safe, since index is always the start of a segment
int iNext = index + 1;
Coordinate next = getCoordinate(iNext);
while (pt.equals2D(next)) {
iNext = next(iNext);
next = getCoordinate(iNext);
}
return next;
}
/**
* Gets the index of the previous segment in the ring.
*
* @param index a segment index
* @return the index of the previous segment
*/
public int prev(int index) {
if (index == 0)
return size() - 2;
return index - 1;
}
/**
* Gets the index of the next segment in the ring.
*
* @param index a segment index
* @return the index of the next segment
*/
public int next(int index) {
if (index < size() - 2)
return index + 1;
return 0;
}
public void createInvalidLines(GeometryFactory geomFactory, List<LineString> lines) {
//-- empty case
if (! hasInvalid()) {
return;
}
//-- entire ring case
if (isInvalid()) {
LineString line = createLine(0, size() - 1, geomFactory);
lines.add(line);
return;
}
//-- find first end after index 0, to allow wrap-around
int startIndex = findInvalidStart(0);
int firstEndIndex = findInvalidEnd(startIndex);
int endIndex = firstEndIndex;
while (true) {
startIndex = findInvalidStart(endIndex);
endIndex = findInvalidEnd(startIndex);
LineString line = createLine(startIndex, endIndex, geomFactory);
lines.add(line);
if (endIndex == firstEndIndex)
break;
}
}
private int findInvalidStart(int index) {
while (! isInvalid(index)) {
index = nextMarkIndex(index);
}
return index;
}
private int findInvalidEnd(int index) {
index = nextMarkIndex(index);
while (isInvalid(index)) {
index = nextMarkIndex(index);
}
return index;
}
private int nextMarkIndex(int index) {
if (index >= isInvalid.length - 1) {
return 0;
}
return index + 1;
}
/**
* Creates a line from a sequence of ring segments between startIndex and endIndex (inclusive).
* If the endIndex < startIndex the sequence wraps around the ring endpoint.
*
* @param startIndex
* @param endIndex
* @param geomFactory
* @return a line representing the section
*/
private LineString createLine(int startIndex, int endIndex, GeometryFactory geomFactory) {
Coordinate[] pts = endIndex < startIndex ?
extractSectionWrap(startIndex, endIndex)
: extractSection(startIndex, endIndex);
return geomFactory.createLineString(pts);
}
private Coordinate[] extractSection(int startIndex, int endIndex) {
int size = endIndex - startIndex + 1;
Coordinate[] pts = new Coordinate[size];
int ipts = 0;
for (int i = startIndex; i <= endIndex; i++) {
pts[ipts++] = getCoordinate(i).copy();
}
return pts;
}
private Coordinate[] extractSectionWrap(int startIndex, int endIndex) {
int size = endIndex + (size() - startIndex);
Coordinate[] pts = new Coordinate[size];
int index = startIndex;
for (int i = 0; i < size; i++) {
pts[i] = getCoordinate(index).copy();
index = nextMarkIndex(index);
}
return pts;
}
}