452 lines
12 KiB
Java
452 lines
12 KiB
Java
/* Copyright 2013 The jeo project. All rights reserved.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
package org.oscim.utils.geom;
|
|
|
|
import org.locationtech.jts.geom.Coordinate;
|
|
import org.locationtech.jts.geom.Geometry;
|
|
import org.locationtech.jts.geom.GeometryCollection;
|
|
import org.locationtech.jts.geom.GeometryFactory;
|
|
import org.locationtech.jts.geom.LineString;
|
|
import org.locationtech.jts.geom.LinearRing;
|
|
import org.locationtech.jts.geom.MultiLineString;
|
|
import org.locationtech.jts.geom.MultiPoint;
|
|
import org.locationtech.jts.geom.MultiPolygon;
|
|
import org.locationtech.jts.geom.Point;
|
|
import org.locationtech.jts.geom.Polygon;
|
|
import org.locationtech.jts.geom.PrecisionModel;
|
|
|
|
import java.lang.reflect.Array;
|
|
import java.util.ArrayDeque;
|
|
import java.util.Arrays;
|
|
import java.util.Deque;
|
|
import java.util.Iterator;
|
|
|
|
/**
|
|
* Builder for geometry objects.
|
|
* <p>
|
|
* Example usage:
|
|
* <p/>
|
|
* <pre>
|
|
* <code>
|
|
* GeometryBuilder gb = new GeometryBuilder();
|
|
*
|
|
* // create array two 2d points and turn into a line string
|
|
* gb.points(1,2,3,4,5,6).toLineString();
|
|
*
|
|
* // build a polygon with holes
|
|
* gb.points(0,0,10,0,10,10,0,10,0,0).ring()
|
|
* .points(4,4,6,4,6,6,4,6,4,4).ring()
|
|
* .toPolygon();
|
|
*
|
|
* </code>
|
|
* </pre>
|
|
* <p/>
|
|
* </p>
|
|
*
|
|
* @author Justin Deoliveira, OpenGeo
|
|
*/
|
|
public class GeomBuilder {
|
|
|
|
GeometryFactory factory;
|
|
Deque<Coordinate> cstack = new ArrayDeque<Coordinate>();
|
|
Deque<Geometry> gstack = new ArrayDeque<Geometry>();
|
|
|
|
/**
|
|
* Constructs a builder with the default geometry factory.
|
|
*/
|
|
public GeomBuilder() {
|
|
this(new GeometryFactory());
|
|
}
|
|
|
|
/**
|
|
* Constructs a builder with an explicit geometry factory.
|
|
*/
|
|
public GeomBuilder(GeometryFactory factory) {
|
|
this.factory = factory;
|
|
}
|
|
|
|
/**
|
|
* Constructs a builder with a specific srid for geometry objects.
|
|
*/
|
|
public GeomBuilder(int srid) {
|
|
this.factory = new GeometryFactory(new PrecisionModel(), srid);
|
|
}
|
|
|
|
/**
|
|
* Adds a 2d point to the coordinate stack.
|
|
*/
|
|
public GeomBuilder point(double x, double y) {
|
|
cstack.push(new Coordinate(x, y));
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Adds a 3d point to the coordinate stack.
|
|
*/
|
|
public GeomBuilder pointz(double x, double y, double z) {
|
|
cstack.push(new Coordinate(x, y, z));
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Adds an array of 2d points to the coordinate stack.
|
|
*/
|
|
public GeomBuilder points(double... ord) {
|
|
if (ord.length % 2 != 0) {
|
|
throw new IllegalArgumentException("Must specify even number of ordinates");
|
|
}
|
|
|
|
for (int i = 0; i < ord.length; i += 2) {
|
|
point(ord[i], ord[i + 1]);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Adds an array of 3d points to the coordinate stack.
|
|
*/
|
|
public GeomBuilder pointsz(double... ord) {
|
|
if (ord.length % 3 != 0) {
|
|
throw new IllegalArgumentException("Must specify ordinates as triples");
|
|
}
|
|
|
|
for (int i = 0; i < ord.length; i += 3) {
|
|
pointz(ord[i], ord[i + 1], ord[i + 2]);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Creates a Point from the last point on the coordinate stack, and places
|
|
* the result
|
|
* on the geometry stack.
|
|
*/
|
|
public GeomBuilder point() {
|
|
gstack.push(factory.createPoint(cpop()));
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Creates a LineString from all points on the coordinate stack, and places
|
|
* the result
|
|
* on the geometry stack.
|
|
*/
|
|
public GeomBuilder lineString() {
|
|
gstack.push(factory.createLineString(cpopAll()));
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Creates a LinearRing from all points on the coordinate stack, and places
|
|
* the result
|
|
* on the geometry stack.
|
|
* <p>
|
|
* If the first and last coordinate on the point stack are not equal an
|
|
* additional point will be added.
|
|
* </p>
|
|
*/
|
|
public GeomBuilder ring() {
|
|
Coordinate[] coords = cpopAll();
|
|
if (coords.length > 1 && !coords[0].equals(coords[coords.length - 1])) {
|
|
Coordinate[] tmp = new Coordinate[coords.length + 1];
|
|
System.arraycopy(coords, 0, tmp, 0, coords.length);
|
|
tmp[tmp.length - 1] = new Coordinate(tmp[0]);
|
|
coords = tmp;
|
|
}
|
|
|
|
gstack.push(factory.createLinearRing(coords));
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Creates a Polygon from all LinearRings on the geometry stack and places
|
|
* the result back
|
|
* on the geometry stack.
|
|
*/
|
|
public GeomBuilder polygon() {
|
|
if (gstack.isEmpty() || !(gstack.peek() instanceof LinearRing)) {
|
|
ring();
|
|
}
|
|
|
|
LinearRing[] rings = gpopAll(LinearRing.class);
|
|
LinearRing outer = rings[0];
|
|
LinearRing[] inner = null;
|
|
if (rings.length > 1) {
|
|
inner = Arrays.copyOfRange(rings, 1, rings.length);
|
|
}
|
|
|
|
gstack.push(factory.createPolygon(outer, inner));
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Creates a MultiPoint from all coordinates on the coordinate stack,
|
|
* plaching the result
|
|
* back on the geometry stack.
|
|
* <p>
|
|
* If the coordinate stack is empty this method will consume all Point
|
|
* geometries on the geometry stack.
|
|
* </p>
|
|
*/
|
|
public GeomBuilder multiPoint() {
|
|
if (!cstack.isEmpty()) {
|
|
gstack.push(factory.createMultiPoint(cpopAll()));
|
|
} else {
|
|
gstack.push(factory.createMultiPoint(gpopAll(Point.class)));
|
|
}
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Creates a MultiLineString from all LineStrings on the geometry stack and
|
|
* places the result
|
|
* back on the geometry stack.
|
|
*/
|
|
public GeomBuilder multiLineString() {
|
|
gstack.push(factory.createMultiLineString(gpopAll(LineString.class)));
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Creates a MultiPolygon from all Polygons on the geometry stack and places
|
|
* the result
|
|
* back on the geometry stack.
|
|
*/
|
|
public GeomBuilder multiPolygon() {
|
|
gstack.push(factory.createMultiPolygon(gpopAll(Polygon.class)));
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Creates a GeometryCollection from all Geometries on the geometry stack
|
|
* and places the result
|
|
* back on the geometry stack.
|
|
*/
|
|
public GeomBuilder collection() {
|
|
gstack.push(factory.createGeometryCollection(gpopAll(Geometry.class)));
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Buffers the geometry at the top of the geometry stack, and places the
|
|
* result back on the
|
|
* geometry stack.
|
|
*/
|
|
public GeomBuilder buffer(double amt) {
|
|
gstack.push(gpop(Geometry.class).buffer(amt));
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Consumes the top of the geometry stack.
|
|
*/
|
|
public Geometry get() {
|
|
return gpop(Geometry.class);
|
|
}
|
|
|
|
/**
|
|
* Builds and returns a Point.
|
|
* <p>
|
|
* This method is equivalent to:
|
|
* <p/>
|
|
* <pre>
|
|
* (Point) point().get();
|
|
* </pre>
|
|
* <p/>
|
|
* </p>
|
|
*/
|
|
public Point toPoint() {
|
|
return point().gpop(Point.class);
|
|
}
|
|
|
|
/**
|
|
* Builds and returns a LineString.
|
|
* <p>
|
|
* This method is equivalent to:
|
|
* <p/>
|
|
* <pre>
|
|
* (LineString) lineString().get();
|
|
* </pre>
|
|
* <p/>
|
|
* </p>
|
|
*/
|
|
public LineString toLineString() {
|
|
return lineString().gpop(LineString.class);
|
|
}
|
|
|
|
/**
|
|
* Builds and returns a LineString.
|
|
* <p>
|
|
* This method is equivalent to:
|
|
* <p/>
|
|
* <pre>
|
|
* (LinearRing) ring().get();
|
|
* </pre>
|
|
* <p/>
|
|
* </p>
|
|
*/
|
|
public LinearRing toLinearRing() {
|
|
return ring().gpop(LinearRing.class);
|
|
}
|
|
|
|
/**
|
|
* Builds and returns a Polygon.
|
|
* <p>
|
|
* This method is equivalent to:
|
|
* <p/>
|
|
* <pre>
|
|
* (Polygon) polygon().get();
|
|
* </pre>
|
|
* <p/>
|
|
* </p>
|
|
*/
|
|
public Polygon toPolygon() {
|
|
return polygon().gpop(Polygon.class);
|
|
}
|
|
|
|
/**
|
|
* Builds and returns a MultiPoint.
|
|
* <p>
|
|
* This method is equivalent to:
|
|
* <p/>
|
|
* <pre>
|
|
* (MultiPoint) multiPoint().get();
|
|
* </pre>
|
|
* <p/>
|
|
* </p>
|
|
*/
|
|
public MultiPoint toMultiPoint() {
|
|
return multiPoint().gpop(MultiPoint.class);
|
|
}
|
|
|
|
/**
|
|
* Builds and returns a MultiLineString.
|
|
* <p>
|
|
* This method is equivalent to:
|
|
* <p/>
|
|
* <pre>
|
|
* (MultiLineString) multiLineString().get();
|
|
* </pre>
|
|
* <p/>
|
|
* </p>
|
|
*/
|
|
public MultiLineString toMultiLineString() {
|
|
return multiLineString().gpop(MultiLineString.class);
|
|
}
|
|
|
|
/**
|
|
* Builds and returns a MultiPolygon.
|
|
* <p>
|
|
* This method is equivalent to:
|
|
* <p/>
|
|
* <pre>
|
|
* (MultiPolygon) multiPolygon().get();
|
|
* </pre>
|
|
* <p/>
|
|
* </p>
|
|
*/
|
|
public MultiPolygon toMultiPolygon() {
|
|
return multiPolygon().gpop(MultiPolygon.class);
|
|
}
|
|
|
|
/**
|
|
* Builds and returns a GEometryCollection.
|
|
* <p>
|
|
* This method is equivalent to:
|
|
* <p/>
|
|
* <pre>
|
|
* (GeometryCollection) collection().get();
|
|
* </pre>
|
|
* <p/>
|
|
* </p>
|
|
*/
|
|
public GeometryCollection toCollection() {
|
|
return collection().gpop(GeometryCollection.class);
|
|
}
|
|
|
|
Coordinate cpop() {
|
|
return cpop(1)[0];
|
|
}
|
|
|
|
Coordinate[] cpop(int n) {
|
|
if (cstack.size() < n) {
|
|
throw new IllegalStateException(String.format("Expected %d values on coordinate stack, "
|
|
+ "but found %d",
|
|
n,
|
|
cstack.size()));
|
|
}
|
|
|
|
Coordinate[] c = new Coordinate[n];
|
|
for (int i = 0; i < n; i++) {
|
|
c[n - i - 1] = cstack.pop();
|
|
}
|
|
return c;
|
|
}
|
|
|
|
Coordinate[] cpopAll() {
|
|
if (cstack.isEmpty()) {
|
|
throw new IllegalStateException("Coordinate stack is empty");
|
|
}
|
|
|
|
return cpop(cstack.size());
|
|
}
|
|
|
|
<T extends Geometry> T gpop(Class<T> clazz) {
|
|
return gpop(1, clazz)[0];
|
|
}
|
|
|
|
<T extends Geometry> T[] gpop(int n, Class<T> clazz) {
|
|
if (gstack.size() < n) {
|
|
throw new IllegalStateException(String.format("Expected %d values on geometry stack, "
|
|
+ "but found %d", n, gstack.size()));
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
T[] l = (T[]) Array.newInstance(clazz, n);
|
|
for (int i = 0; i < n; i++) {
|
|
Object g = gstack.pop();
|
|
if (!clazz.isInstance(g)) {
|
|
throw new IllegalStateException(String.format("Expected %s on geometry stack, but "
|
|
+ "found %s", clazz.getSimpleName(), g.getClass().getSimpleName()));
|
|
}
|
|
|
|
l[n - i - 1] = clazz.cast(g);
|
|
}
|
|
return l;
|
|
}
|
|
|
|
<T extends Geometry> T[] gpopAll(Class<T> clazz) {
|
|
if (gstack.isEmpty()) {
|
|
throw new IllegalArgumentException("Geometry stack is empty");
|
|
}
|
|
|
|
int n = 0;
|
|
Iterator<Geometry> it = gstack.iterator();
|
|
while (it.hasNext() && clazz.isInstance(it.next())) {
|
|
n++;
|
|
}
|
|
|
|
if (n == 0) {
|
|
throw new IllegalArgumentException(
|
|
String.format("Expected %s on geometry stack",
|
|
clazz.getSimpleName()));
|
|
}
|
|
|
|
return gpop(n, clazz);
|
|
}
|
|
}
|