/* 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 io.jeo.geom;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.prep.PreparedGeometry;
import com.vividsolutions.jts.geom.prep.PreparedGeometryFactory;
import com.vividsolutions.jts.io.WKTReader;
import io.jeo.geojson.GeoJSONReader;
import io.jeo.geojson.GeoJSONWriter;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryCollection;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPoint;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
/**
* Geometry module utility module.
*
* @author Justin Deoliveira, OpenGeo
*/
public class Geom {
/**
* static default factory
*/
public final static GeometryFactory factory = new GeometryFactory();
/**
* Geometry type enumeration.
*/
public enum Type {
POINT(Point.class),
LINESTRING(LineString.class),
POLYGON(Polygon.class),
MULTIPOINT(MultiPoint.class),
MULTILINESTRING(MultiLineString.class),
MULTIPOLYGON(MultiPolygon.class),
GEOMETRY(Geometry.class),
GEOMETRYCOLLECTION(GeometryCollection.class);
private final Class<? extends Geometry> type;
private final String name;
private final String simpleName;
Type(Class<? extends Geometry> type) {
this.type = type;
this.name = type.getSimpleName();
this.simpleName = (name.startsWith("Multi") ? name.substring(5) : name);
}
/**
* Return the {@code Geometry} class associated with this type.
*
* @return the {@code Geometry} class
*/
public Class<? extends Geometry> getType() {
return type;
}
/**
* Equivalent to {@linkplain #getName()}.
*
* @return the name of this type
*/
@Override
public String toString() {
return name;
}
/**
* Return a name for this type that is suitable for text descriptions.
*
* @return the name
*/
public String getName() {
return name;
}
/**
* Get the 'simple name'. Returns the same value as {@linkplain #getName()}
* except for MULTIPOINT, MULTILINESTRING and MULTIPOLYGON, for which it returns
* the name without the 'Multi' prefix.
*
* @return the simple name
*/
public String getSimpleName() {
return simpleName;
}
/**
* Get the {@code Geometries} for the given object.
*
* @param geom a JTS Geometry object
*
* @return the {@code Geometries} for the argument's class, or {@code null}
* if the argument is {@code null}
*/
public static Type from(Geometry geom) {
if (geom != null) {
return from(geom.getClass());
}
return null;
}
/**
* Get the {@code Geometries} for the given {@code Geometry} class.
*
* @param geomClass the class
*
* @return the constant for this class
*/
public static Type from(Class<?> geomClass) {
for (Type gt : Type.values()) {
if (gt.type == geomClass) {
return gt;
}
}
//no direct match look for a subclass
Type match = null;
for (Type gt : Type.values()) {
if (gt == GEOMETRY || gt == GEOMETRYCOLLECTION) {
continue;
}
if (gt.type.isAssignableFrom(geomClass)) {
if (match == null) {
match = gt;
} else {
// more than one match
return null;
}
}
}
if (match == null) {
//no matches from concrete classes, try abstract classes
if (GeometryCollection.class.isAssignableFrom(geomClass)) {
return GEOMETRYCOLLECTION;
}
if (Geometry.class.isAssignableFrom(geomClass)) {
return GEOMETRY;
}
}
return match;
}
/**
* Get the {@code Geometries} for the specified name.
*
* @param name The name of the geometry, eg: "POINT"
*
* @return The constant for the name.
*/
public static Type from(String name) {
for (Type gt : Type.values()) {
if (gt.getName().equalsIgnoreCase(name)) {
return gt;
}
}
return null;
}
}
/**
* Creates a new geometry builder.
*/
public static GeomBuilder build() {
return new GeomBuilder();
}
/**
* Convenience method to build a Point geometry.
*/
public static Point point(double x, double y) {
return build().point(x, y).toPoint();
}
/**
* Convenience method to build a LineString geometry.
*
* @param ord Even number of ordinates forming coordinates for the line string.
*/
public static LineString lineString(double... ord) {
return build().points(ord).toLineString();
}
/**
* Convenience method to build a Polygon geometry.
*
* @param ord Even number of ordinates forming coordinates for the outer ring of the polygon.
*/
public static Polygon polygon(double... ord) {
return build().points(ord).toPolygon();
}
/**
* Returns an iterable over the points of a multipoint.
*/
public static Iterable<Point> iterate(MultiPoint mp) {
return new GeometryIterable<Point>(mp);
}
/**
* Returns an iterable over the lines of a multilinestring.
*/
public static Iterable<LineString> iterate(MultiLineString ml) {
return new GeometryIterable<LineString>(ml);
}
/**
* Returns an iterable over the polygons of a multipolygon.
*/
public static Iterable<Polygon> iterate(MultiPolygon mp) {
return new GeometryIterable<Polygon>(mp);
}
/**
* Returns an iterable over the geometries of a geometry collection..
*/
public static Iterable<Geometry> iterate(GeometryCollection gc) {
return new GeometryIterable<Geometry>(gc);
}
/**
* Returns an iterable over the interior rings of a polygon.
*/
public static Iterable<LineString> holes(final Polygon p) {
return new Iterable<LineString>() {
int i = 0;
@Override
public Iterator<LineString> iterator() {
return new Iterator<LineString>() {
@Override
public boolean hasNext() {
return i < p.getNumInteriorRing();
}
@Override
public LineString next() {
return p.getInteriorRingN(i++);
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
};
}
/**
* Returns the first point in a multi point, or <code>null</code> if the multi point is empty.
*/
public static Point first(MultiPoint mp) {
return mp.getNumGeometries() > 0 ? (Point) mp.getGeometryN(0) : null;
}
/**
* Returns the first line in a multi line, or <code>null</code> if the multi line is empty.
*/
public static LineString first(MultiLineString ml) {
return ml.getNumGeometries() > 0 ? (LineString) ml.getGeometryN(0) : null;
}
/**
* Returns the first polygon in a multi polygon, or <code>null</code> if the multi polygon is empty.
*/
public static Polygon first(MultiPolygon mp) {
return mp.getNumGeometries() > 0 ? (Polygon) mp.getGeometryN(0) : null;
}
/**
* Returns the geometries of a geometry collection as an array.
*/
public static <T extends Geometry> T[] array(GeometryCollection gc, T[] array) {
for (int i =0 ; i < gc.getNumGeometries(); i++) {
array[i] = (T) gc.getGeometryN(i);
}
return array;
}
/**
* Retypes (ie. narrows) a geometry collection if possible.
* <p>
* If the geometry contains a single object it is narrowed to that object. If the geometry collection is
* homogeneous it is narrowed to the appropriate sub collection type. Otherwise the collection is returned
* as is.
* </p>
*
* @see {@link GeometryFactory#buildGeometry(Collection)}
*/
public static <T extends Geometry> T narrow(GeometryCollection gc) {
if (gc.getNumGeometries() == 0) {
return (T) gc;
}
List<Geometry> objects = new ArrayList<>(gc.getNumGeometries());
for (Geometry g : iterate(gc)) {
objects.add(g);
}
return (T) gc.getFactory().buildGeometry(objects);
}
/**
* Recursively flattens a geometry collection into it's constituent geometry objects.
*/
public static <T extends Geometry> List<T> flatten(GeometryCollection gc) {
return flatten(Collections.singletonList((Geometry)gc));
}
/**
* Recursively flattens a list of geometries into it's constituent geometry objects.
*/
public static <T extends Geometry> List<T> flatten(List<Geometry> gl) {
List<T> flat = new ArrayList<>();
LinkedList<Geometry> q = new LinkedList<>();
q.addAll(gl);
while (!q.isEmpty()) {
Geometry g = q.removeFirst();
if (g instanceof GeometryCollection) {
for (Geometry h : iterate((GeometryCollection)g)) {
q.addLast(h);
}
}
else {
flat.add((T) g);
}
}
return flat;
}
/**
* Recursively unwraps a geometry collection containing single object.
*/
public static Geometry singlify(Geometry geom) {
while (geom instanceof GeometryCollection && geom.getNumGeometries() == 1) {
geom = geom.getGeometryN(0);
}
return geom;
}
/*
* Private iterable class.
*/
private static class GeometryIterable<T extends Geometry> implements Iterable<T> {
GeometryCollection gc;
GeometryIterable(GeometryCollection gc) {
this.gc = gc;
}
@Override
public Iterator<T> iterator() {
return new GeometryIterator<T>(gc);
}
}
/*
* Private iterator class.
*/
private static class GeometryIterator<T extends Geometry> implements Iterator<T> {
int i = 0;
GeometryCollection gc;
GeometryIterator(GeometryCollection gc) {
this.gc = gc;
}
@Override
public boolean hasNext() {
return i < gc.getNumGeometries();
}
@SuppressWarnings("unchecked")
@Override
public T next() {
return (T) gc.getGeometryN(i++);
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
/**
* Returns the GeoJSON representation of the Geometry object.
*
* @param g The geometry.
*
* @return The GeoJSON string.
*
* @see {@Link GeoJSONWriter}
*/
public static String json(Geometry g) {
return GeoJSONWriter.toString(g);
}
/**
* Creates a prepared geometry.
* <p>
* Prepared geometries make operations like intersection must faster.
* </p>
*/
public static PreparedGeometry prepare(Geometry g) {
return PreparedGeometryFactory.prepare(g);
}
/**
* Converts a geometry object into the associated geometry collection. For
* example Polygon to MultiPolygon.
* <p>
* If the input is already a collection it is returned as is.
* </p>
*/
public static GeometryCollection multi(Geometry g) {
switch(Geom.Type.from(g)) {
case POINT:
return factory.createMultiPoint(new Point[]{(Point)g});
case LINESTRING:
return factory.createMultiLineString(new LineString[]{(LineString)g});
case POLYGON:
return factory.createMultiPolygon(new Polygon[]{(Polygon)g});
default:
return (GeometryCollection) g;
}
}
/**
* Visits a geometry object.
*
* @param g The geometry.
* @param v The visitor.
*/
public static void visit(Geometry g, GeometryVisitor v) {
ArrayDeque<Geometry> stack = new ArrayDeque<>();
stack.push(g);
while (!stack.isEmpty()) {
g = stack.pop();
switch(Type.from(g)) {
case POINT:
v.visit((Point)g);
break;
case LINESTRING:
v.visit((LineString)g);
break;
case POLYGON: {
Polygon p = (Polygon) g;
v.visit(p);
if (v.descend(p)) {
stack.push(p.getExteriorRing());
for (int i = 0; i < p.getNumInteriorRing(); i++) {
stack.push(p.getInteriorRingN(i));
}
}
break;
}
case MULTIPOINT: {
MultiPoint mp = (MultiPoint) g;
v.visit(mp);
if (v.descend(mp)) {
for (Point p : iterate(mp)) {
stack.push(p);
}
}
break;
}
case MULTILINESTRING: {
MultiLineString ml = (MultiLineString) g;
v.visit(ml);
if (v.descend(ml)) {
for (LineString l : iterate(ml)) {
stack.push(l);
}
}
break;
}
case MULTIPOLYGON: {
MultiPolygon mp = (MultiPolygon) g;
v.visit(mp);
if (v.descend(mp)) {
for (Polygon p : iterate(mp)) {
stack.push(p);
}
}
break;
}
case GEOMETRYCOLLECTION: {
GeometryCollection gc = (GeometryCollection) g;
v.visit(gc);
if (v.descend(gc)) {
for (Geometry obj : iterate(gc)) {
stack.push(obj);
}
}
break;
}
default:
throw new IllegalArgumentException("Unknown geometry type: " + g);
}
}
}
static List<GeomParser> PARSERS = new ArrayList<>();
static {
PARSERS.add(new GeomParser() {
@Override
public Geometry parse(String str) throws Exception {
return new WKTReader().read(str);
}
});
PARSERS.add(new GeomParser() {
@Override
public Geometry parse(String str) throws Exception {
return new GeoJSONReader().geometry(str);
}
});
}
/**
* Parses the specified string into a geometry.
* <p>
* Currently the following geometry formats are supported.
* <ul>
* <li>Well Known Text</li>
* <li>GeoJSON</li>
* </ul>
* </p>
* @param str The geometry string.
*
* @return The parsed geometry, or <code>null</code> if it could not be parsed in on of the formats listed above.
*/
public static Geometry parse(String str) {
for (GeomParser p : PARSERS) {
try {
Geometry g = p.parse(str);
if (g != null) {
return g;
}
}
catch(Exception e) {
}
}
return null;
}
interface GeomParser {
Geometry parse(String str) throws Exception;
}
}