Geom.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.locationtech.spatial4j.util;
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 org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.prep.PreparedGeometry;
import org.locationtech.jts.geom.prep.PreparedGeometryFactory;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryCollection;
import org.locationtech.jts.geom.LineString;
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;
/**
* Geometry module utility module.
*
* <p>
* Class taken from http://github.com/jeo/jeo, Nov 2017 by Justin Deoliveira.
* </p>
*
* @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<>(mp);
}
/**
* Returns an iterable over the lines of a multilinestring.
*/
public static Iterable<LineString> iterate(MultiLineString ml) {
return new GeometryIterable<>(ml);
}
/**
* Returns an iterable over the polygons of a multipolygon.
*/
public static Iterable<Polygon> iterate(MultiPolygon mp) {
return new GeometryIterable<>(mp);
}
/**
* Returns an iterable over the geometries of a geometry collection..
*/
public static Iterable<Geometry> iterate(GeometryCollection gc) {
return new GeometryIterable<>(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.
*/
@SuppressWarnings("unchecked")
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 Geometry narrow(GeometryCollection gc) {
if (gc.getNumGeometries() == 0) {
return gc;
}
List<Geometry> objects = new ArrayList<>(gc.getNumGeometries());
for (Geometry g : iterate(gc)) {
objects.add(g);
}
return gc.getFactory().buildGeometry(objects);
}
/**
* Recursively flattens a geometry collection into it's constituent geometry objects.
*/
public static List<Geometry> flatten(GeometryCollection gc) {
return flatten(Collections.singletonList(gc));
}
/**
* Recursively flattens a list of geometries into it's constituent geometry objects.
*/
public static List<Geometry> flatten(List<Geometry> gl) {
List<Geometry> flat = new ArrayList<>();
LinkedList<Geometry> q = new LinkedList<>(gl);
while (!q.isEmpty()) {
Geometry g = q.removeFirst();
if (g instanceof GeometryCollection) {
for (Geometry h : iterate((GeometryCollection)g)) {
q.addLast(h);
}
}
else {
flat.add(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<>(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();
}
}
/**
* 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;
}
}
}