Skip to content

Commit

Permalink
Merge pull request #3 from locationtech/master
Browse files Browse the repository at this point in the history
update fork
  • Loading branch information
mukoki authored Aug 31, 2020
2 parents dd04203 + 85b9cef commit 6bb5ae7
Show file tree
Hide file tree
Showing 10 changed files with 362 additions and 230 deletions.
10 changes: 10 additions & 0 deletions doc/JTS_Version_History.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@ Distributions for older JTS versions can be obtained at the

*Release Date: TBD*

### Functionality Improvements

* Improve Orientation.isCCW to handle flat topology collapse (#588)

<!-- ================================================================ -->

# Version 1.17.1

*Release Date: August 27, 2020*

*Java Version: 1.8*

### Functionality Improvements
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,18 @@ public class Palette {
public static final int TYPE_RAINBOW = 3;
public static final int TYPE_RAINBOW_RANDOM = 4;

private static final HSBPalette PAL_RAINBOW_INCREMENTAL = HSBPalette.createRainbowIncremental(0.396f, 0.4f, 1);

private static final float BRIGHT_RANGE = 0.1f;
private static final float SAT_RANGE = 0.2f;

public static HSBPalette customPalette(int paletteType, Color clrBase, int numHues) {
HSBPalette pal = null;
float sat = 0.6f; //ColorUtil.getSaturation(clrBase);
float sat = ColorUtil.getSaturation(clrBase);
float bright = ColorUtil.getBrightness(clrBase);
if (TYPE_VARY == paletteType) {
float hue = ColorUtil.getHue(clrBase);
pal = new HSBPalette(5, hue, 0.1f,
3, 0.3f, 0.7f,
3, 0.8f, 0.9f
pal = new HSBPalette(5, hue, HSBPalette.HUE_WIDTH / 2,
3, sat - SAT_RANGE/2, sat + SAT_RANGE/2,
3, bright - BRIGHT_RANGE/2, bright + BRIGHT_RANGE/2
);
}
else if (TYPE_RAINBOW == paletteType) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@

import java.awt.Color;

import org.locationtech.jts.math.MathUtil;
import org.locationtech.jtstest.testbuilder.ui.ColorUtil;

public class HSBPalette {

public static final float HUE_WIDTH = 0.08333f;
private static final float HUE_WRAP_MAX = 1 - HUE_WIDTH;

public static HSBPalette createRainbow(int numHue, float s, float b) {
return new HSBPalette(numHue, 0, 1 - 0.08333f,
return new HSBPalette(numHue, 0, HUE_WRAP_MAX,
1, s, s,
1, b, b
);
Expand Down Expand Up @@ -77,8 +81,8 @@ public Color color(int index, int alpha) {
int iS = iSB / numB;
int iB = iSB - iS * numB;
float h = (h1 + iH * hInc) % 1.0f;
float s = sLo + iS * sInc;
float b = bLo + iB * bInc;
float s = (float) MathUtil.clamp(sLo + iS * sInc, 0, 1);
float b = (float) MathUtil.clamp(bLo + iB * bInc, 0, 1);
Color chsb = Color.getHSBColor(h, s, b);
return ColorUtil.setAlpha(chsb, alpha);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.CoordinateSequence;
import org.locationtech.jts.geom.impl.CoordinateArraySequence;

/**
* Functions to compute the orientation of basic geometric structures
Expand Down Expand Up @@ -107,172 +108,130 @@ public static int index(Coordinate p1, Coordinate p2, Coordinate q)
* oriented counter-clockwise.
* <ul>
* <li>The list of points is assumed to have the first and last points equal.
* <li>This will handle coordinate lists which contain repeated points.
* <li>This handles coordinate lists which contain repeated points.
* <li>This handles rings which contain collapsed segments
* (in particular, along the top of the ring).
* </ul>
* This algorithm is <b>only</b> guaranteed to work with valid rings. If the
* ring is invalid (e.g. self-crosses or touches), the computed result may not
* be correct.
* This algorithm is guaranteed to work with valid rings.
* It also works with "mildly invalid" rings
* which contain collapsed (coincident) flat segments along the top of the ring.
* If the ring is "more" invalid (e.g. self-crosses or touches),
* the computed result may not be correct.
*
* @param ring
* an array of Coordinates forming a ring
* @param ring an array of Coordinates forming a ring (with first and last point identical)
* @return true if the ring is oriented counter-clockwise.
* @throws IllegalArgumentException
* if there are too few points to determine orientation (&lt; 4)
* @throws IllegalArgumentException if there are too few points to determine orientation (&lt; 4)
*/
public static boolean isCCW(Coordinate[] ring)
{
// # of points without closing endpoint
int nPts = ring.length - 1;
// sanity check
if (nPts < 3)
throw new IllegalArgumentException(
"Ring has fewer than 4 points, so orientation cannot be determined");

// find highest point
Coordinate hiPt = ring[0];
int hiIndex = 0;
for (int i = 1; i <= nPts; i++) {
Coordinate p = ring[i];
if (p.y > hiPt.y) {
hiPt = p;
hiIndex = i;
}
}

// find distinct point before highest point
int iPrev = hiIndex;
do {
iPrev = iPrev - 1;
if (iPrev < 0)
iPrev = nPts;
} while (ring[iPrev].equals2D(hiPt) && iPrev != hiIndex);

// find distinct point after highest point
int iNext = hiIndex;
do {
iNext = (iNext + 1) % nPts;
} while (ring[iNext].equals2D(hiPt) && iNext != hiIndex);

Coordinate prev = ring[iPrev];
Coordinate next = ring[iNext];

/*
* This check catches cases where the ring contains an A-B-A configuration
* of points. This can happen if the ring does not contain 3 distinct points
* (including the case where the input array has fewer than 4 elements), or
* it contains coincident line segments.
*/
if (prev.equals2D(hiPt) || next.equals2D(hiPt) || prev.equals2D(next))
return false;

int disc = Orientation.index(prev, hiPt, next);

/*
* If disc is exactly 0, lines are collinear. There are two possible cases:
* (1) the lines lie along the x axis in opposite directions (2) the lines
* lie on top of one another
*
* (1) is handled by checking if next is left of prev ==> CCW (2) will never
* happen if the ring is valid, so don't check for it (Might want to assert
* this)
*/
boolean isCCW;
if (disc == 0) {
// poly is CCW if prev x is right of next x
isCCW = (prev.x > next.x);
}
else {
// if area is positive, points are ordered CCW
isCCW = (disc > 0);
}
return isCCW;
// wrap with an XY CoordinateSequence
return isCCW(new CoordinateArraySequence(ring, 2, 0));
}

/**
* Computes whether a ring defined by an {@link CoordinateSequence} is
* Computes whether a ring defined by a {@link CoordinateSequence} is
* oriented counter-clockwise.
* <ul>
* <li>The list of points is assumed to have the first and last points equal.
* <li>This will handle coordinate lists which contain repeated points.
* <li>This handles coordinate lists which contain repeated points.
* <li>This handles rings which contain collapsed segments
* (in particular, along the top of the ring).
* </ul>
* This algorithm is <b>only</b> guaranteed to work with valid rings. If the
* ring is invalid (e.g. self-crosses or touches), the computed result may not
* be correct.
*
* @param ring
* a CoordinateSequence forming a ring
* This algorithm is guaranteed to work with valid rings.
* It also works with "mildly invalid" rings
* which contain collapsed (coincident) flat segments along the top of the ring.
* If the ring is "more" invalid (e.g. self-crosses or touches),
* the computed result may not be correct.
*
* @param ring a CoordinateSequence forming a ring (with first and last point identical)
* @return true if the ring is oriented counter-clockwise.
* @throws IllegalArgumentException
* if there are too few points to determine orientation (&lt; 4)
*/
* @throws IllegalArgumentException if there are too few points to determine orientation (&lt; 4)
*/
public static boolean isCCW(CoordinateSequence ring)
{
// # of points without closing endpoint
int nPts = ring.size() - 1;
// sanity check
if (nPts < 3)
throw new IllegalArgumentException(
"Ring has fewer than 4 points, so orientation cannot be determined");

// find highest point
Coordinate hiPt = ring.getCoordinate(0);
int hiIndex = 0;
"Ring has fewer than 4 points, so orientation cannot be determined");

/**
* Find first highest point after a lower point, if one exists
* (e.g. a rising segment)
* If one does not exist, hiIndex will remain 0
* and the ring must be flat.
* Note this relies on the convention that
* rings have the same start and end point.
*/
Coordinate upHiPt = ring.getCoordinate(0);
double prevY = upHiPt.y;
Coordinate upLowPt = null;
int iUpHi = 0;
for (int i = 1; i <= nPts; i++) {
Coordinate p = ring.getCoordinate(i);
if (p.y > hiPt.y) {
hiPt = p;
hiIndex = i;
double py = ring.getOrdinate(i, Coordinate.Y);
/**
* If segment is upwards and endpoint is higher, record it
*/
if (py > prevY && py >= upHiPt.y) {
upHiPt = ring.getCoordinate(i);
iUpHi = i;
upLowPt = ring.getCoordinate(i-1);
}
prevY = py;
}

// find distinct point before highest point
Coordinate prev;
int iPrev = hiIndex;
do {
iPrev = iPrev - 1;
if (iPrev < 0)
iPrev = nPts;
prev = ring.getCoordinate(iPrev);
} while (prev.equals2D(hiPt) && iPrev != hiIndex);

// find distinct point after highest point
Coordinate next;
int iNext = hiIndex;
do {
iNext = (iNext + 1) % nPts;
next = ring.getCoordinate(iNext);
} while (next.equals2D(hiPt) && iNext != hiIndex);

/*
* This check catches cases where the ring contains an A-B-A configuration
* of points. This can happen if the ring does not contain 3 distinct points
* (including the case where the input array has fewer than 4 elements), or
* it contains coincident line segments.
/**
* Check if ring is flat and return default value if so
*/
if (prev.equals2D(hiPt) || next.equals2D(hiPt) || prev.equals2D(next))
return false;

int disc = Orientation.index(prev, hiPt, next);
if (iUpHi == 0) return false;

/**
* Find the next lower point after the high point
* (e.g. a falling segment).
* This must exist since ring is not flat.
*/
int iDownLow = iUpHi;
do {
iDownLow = (iDownLow + 1) % nPts;
} while (iDownLow != iUpHi && ring.getOrdinate(iDownLow, Coordinate.Y) == upHiPt.y );

/*
* If disc is exactly 0, lines are collinear. There are two possible cases:
* (1) the lines lie along the x axis in opposite directions (2) the lines
* lie on top of one another
*
* (1) is handled by checking if next is left of prev ==> CCW (2) will never
* happen if the ring is valid, so don't check for it (Might want to assert
* this)
Coordinate downLowPt = ring.getCoordinate(iDownLow);
int iDownHi = iDownLow > 0 ? iDownLow - 1 : nPts - 1;
Coordinate downHiPt = ring.getCoordinate(iDownHi);

/**
* Two cases can occur:
* 1) the hiPt and the downPrevPt are the same.
* This is the general position case of a "pointed cap".
* The ring orientation is determined by the orientation of the cap
* 2) The hiPt and the downPrevPt are different.
* In this case the top of the cap is flat.
* The ring orientation is given by the direction of the flat segment
*/
boolean isCCW;
if (disc == 0) {
// poly is CCW if prev x is right of next x
isCCW = (prev.x > next.x);
if (upHiPt.equals2D(downHiPt)) {
/**
* Check for the case where the cap has configuration A-B-A.
* This can happen if the ring does not contain 3 distinct points
* (including the case where the input array has fewer than 4 elements), or
* it contains coincident line segments.
*/
if (upLowPt.equals2D(upHiPt) || downLowPt.equals2D(upHiPt) || upLowPt.equals2D(downLowPt))
return false;

/**
* It can happen that the top segments are coincident.
* This is an invalid ring, which cannot be computed correctly.
* In this case the orientation is 0, and the result is false.
*/
int index = index(upLowPt, upHiPt, downLowPt);
return index == COUNTERCLOCKWISE;
}
else {
// if area is positive, points are ordered CCW
isCCW = (disc > 0);
/**
* Flat cap - direction of flat top determines orientation
*/
double delX = downHiPt.x - upHiPt.x;
return delX < 0;
}
return isCCW;
}

}
Loading

0 comments on commit 6bb5ae7

Please sign in to comment.