package com.revolsys.geometry.model.impl;
import java.util.AbstractList;
import java.util.Arrays;
import com.revolsys.geometry.model.GeometryFactory;
import com.revolsys.geometry.model.Point;
import com.revolsys.util.function.Function4;
public class SortedPointList extends AbstractList<Point> {
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private final GeometryFactory geometryFactory;
private final int axisCount;
private double[] coordinates;
private int vertexCount;
private Function4<Double, Double, Double, Double, Integer> comparator = (x1, y1, x2, y2) -> {
int compare = Double.compare(y1, y2);
if (compare == 0) {
compare = Double.compare(x1, x2);
}
return compare;
};
public SortedPointList(final GeometryFactory geometryFactory, final int axisCount) {
this(geometryFactory, axisCount, 1);
}
public SortedPointList(final GeometryFactory geometryFactory, final int axisCount,
final Function4<Double, Double, Double, Double, Integer> comparator) {
this(geometryFactory, axisCount, 1);
this.comparator = comparator;
}
public SortedPointList(final GeometryFactory geometryFactory, final int axisCount,
final int initialSize) {
if (axisCount < 2) {
throw new IllegalArgumentException("axisCount=" + axisCount + " must be >= 2");
}
this.geometryFactory = geometryFactory.convertAxisCount(axisCount);
this.axisCount = axisCount;
this.coordinates = new double[axisCount * initialSize];
Arrays.fill(this.coordinates, 0, this.coordinates.length, Double.NaN);
this.vertexCount = 0;
}
public SortedPointList(final GeometryFactory geometryFactory, final int axisCount,
final int initialSize, final Function4<Double, Double, Double, Double, Integer> comparator) {
this(geometryFactory, axisCount, initialSize);
this.comparator = comparator;
}
public int addPoint(final double x, final double y) {
return addPoint(x, y, Double.NaN);
}
public int addPoint(final double x, final double y, final double z) {
if (this.vertexCount == 0) {
insertPoint(0, x, y, z);
return 0;
} else {
int minVertexIndex = 0;
int maxVertexIndex = this.vertexCount - 1;
while (true) {
final int vertexIndex = minVertexIndex + maxVertexIndex >>> 1;
final int coordinateIndex = vertexIndex * this.axisCount;
final double x1 = this.coordinates[coordinateIndex];
final double y1 = this.coordinates[coordinateIndex + 1];
int compare = this.comparator.apply(x, y, x1, y1);
if (compare == 0) {
if (this.axisCount == 2) {
return vertexIndex;
} else {
final double z1 = this.coordinates[coordinateIndex + 2];
compare = Double.compare(z, z1);
if (compare == 0) {
return vertexIndex;
} else if (Double.isFinite(z1)) {
throw new IllegalArgumentException("Duplicate points not supported");
} else if (Double.isFinite(z)) {
this.coordinates[coordinateIndex + 2] = z;
return vertexIndex;
} else {
return vertexIndex;
}
}
}
if (compare == -1) {
if (vertexIndex == minVertexIndex) {
insertPoint(minVertexIndex, x, y, z);
return minVertexIndex;
} else {
maxVertexIndex = vertexIndex - 1;
}
} else if (compare == 1) {
if (vertexIndex == maxVertexIndex) {
final int newVertexIndex = maxVertexIndex + 1;
insertPoint(newVertexIndex, x, y, z);
return newVertexIndex;
} else {
minVertexIndex = vertexIndex + 1;
}
} else {
return vertexIndex;
}
}
}
}
@Override
public SortedPointList clone() {
try {
final SortedPointList clone = (SortedPointList)super.clone();
clone.coordinates = this.coordinates.clone();
return clone;
} catch (final CloneNotSupportedException e) {
return this;
}
}
private void ensureCapacity(final int vertexCount) {
if (vertexCount >= this.vertexCount) {
final int coordinateCount = vertexCount * this.axisCount;
if (coordinateCount - this.coordinates.length > 0) {
grow(coordinateCount);
}
}
}
@Override
public Point get(final int index) {
return getPoint(index);
}
public int getAxisCount() {
return this.axisCount;
}
public double getCoordinate(final int index, final int axisIndex) {
final int axisCount = getAxisCount();
if (index >= 0 && index < this.vertexCount && axisIndex < axisCount) {
return this.coordinates[index * axisCount + axisIndex];
} else {
return Double.NaN;
}
}
public double[] getCoordinates() {
final double[] coordinates = new double[this.vertexCount * this.axisCount];
System.arraycopy(this.coordinates, 0, coordinates, 0, coordinates.length);
return coordinates;
}
public double[] getCoordinatesRaw() {
return this.coordinates;
}
public GeometryFactory getGeometryFactory() {
return this.geometryFactory;
}
public Point getPoint(final int index) {
final double[] coordinates = new double[this.axisCount];
System.arraycopy(this.coordinates, index * this.axisCount, coordinates, 0, this.axisCount);
return this.geometryFactory.point(coordinates);
}
public int getVertexCount() {
return this.vertexCount;
}
private void grow(final int minLength) {
final int oldLength = this.coordinates.length;
int newLength;
if (oldLength == 0) {
newLength = 8 * this.axisCount;
} else if (oldLength < 768) {
newLength = oldLength + (oldLength >> 1) * this.axisCount;
} else {
newLength = oldLength + 768;
}
if (newLength - minLength < 0) {
newLength = minLength;
}
if (newLength - MAX_ARRAY_SIZE > 0) {
if (minLength < 0) {
throw new OutOfMemoryError();
} else {
newLength = minLength > MAX_ARRAY_SIZE ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
}
this.coordinates = Arrays.copyOf(this.coordinates, newLength);
Arrays.fill(this.coordinates, oldLength, this.coordinates.length, Double.NaN);
}
@Override
public int indexOf(final Object other) {
if (other instanceof Point) {
final Point point = (Point)other;
return indexOfPoint(point);
}
return super.indexOf(other);
}
public int indexOfPoint(final double x, final double y) {
return indexOfPoint(x, y, Double.NaN);
}
public int indexOfPoint(final double x, final double y, final double z) {
if (this.vertexCount == 0) {
return -1;
} else {
int minVertexIndex = 0;
int maxVertexIndex = this.vertexCount - 1;
while (minVertexIndex <= maxVertexIndex) {
final int vertexIndex = minVertexIndex + maxVertexIndex >>> 1;
final int coordinateIndex = vertexIndex * this.axisCount;
final double x1 = this.coordinates[coordinateIndex];
final double y1 = this.coordinates[coordinateIndex + 1];
int compare = this.comparator.apply(x, y, x1, y1);
if (compare == 0) {
if (this.axisCount == 2) {
return vertexIndex;
} else {
final double z1 = this.coordinates[coordinateIndex + 2];
compare = Double.compare(z, z1);
if (compare == 0) {
return vertexIndex;
} else if (Double.isFinite(z1)) {
if (!Double.isFinite(z)) {
return vertexIndex;
}
} else if (Double.isFinite(z)) {
return vertexIndex;
} else {
return vertexIndex;
}
}
}
if (compare == -1) {
if (vertexIndex == minVertexIndex) {
return -1;
} else {
maxVertexIndex = vertexIndex - 1;
}
} else if (compare == 1) {
if (vertexIndex == maxVertexIndex) {
return -1;
} else {
minVertexIndex = vertexIndex + 1;
}
} else {
return vertexIndex;
}
}
}
return -1;
}
public int indexOfPoint(final Point point) {
final double x = point.getX();
final double y = point.getY();
final double z = point.getZ();
return indexOfPoint(x, y, z);
}
private void insertPoint(final int index, final double x, final double y, final double z) {
final int axisCount = getAxisCount();
if (index >= this.vertexCount) {
ensureCapacity(index + 1);
this.vertexCount = index + 1;
} else {
ensureCapacity(this.vertexCount + 1);
final int offset = index * axisCount;
final int newOffset = offset + axisCount;
System.arraycopy(this.coordinates, offset, this.coordinates, newOffset,
this.coordinates.length - newOffset);
this.vertexCount++;
}
final int offset = index * axisCount;
this.coordinates[offset] = x;
this.coordinates[offset + 1] = y;
if (axisCount > 2) {
this.coordinates[offset + 2] = z;
}
}
@Override
public boolean isEmpty() {
return this.vertexCount == 0;
}
@Override
public int size() {
return getVertexCount();
}
}