org.integratedmodelling.engine.geospace.literals.ShapeValue Maven / Gradle / Ivy
The newest version!
/*******************************************************************************
* Copyright (C) 2007, 2015:
*
* - Ferdinando Villa
* - integratedmodelling.org
* - any other authors listed in @author annotations
*
* All rights reserved. This file is part of the k.LAB software suite,
* meant to enable modular, collaborative, integrated
* development of interoperable data and model components. For
* details, see http://integratedmodelling.org.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the Affero General Public License
* Version 3 or any later version.
*
* This program is distributed in the hope that it will be useful,
* but without any warranty; without even the implied warranty of
* merchantability or fitness for a particular purpose. See the
* Affero General Public License for more details.
*
* You should have received a copy of the Affero General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
* The license is also available at: https://www.gnu.org/licenses/agpl.html
*******************************************************************************/
package org.integratedmodelling.engine.geospace.literals;
import org.geotools.geometry.jts.JTS;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.CRS;
import org.integratedmodelling.api.lang.IMetadataHolder;
import org.integratedmodelling.api.lang.IParseable;
import org.integratedmodelling.api.metadata.IMetadata;
import org.integratedmodelling.api.modelling.ITopologicallyComparable;
import org.integratedmodelling.api.space.IGrid;
import org.integratedmodelling.api.space.IGrid.Cell;
import org.integratedmodelling.api.space.IShape;
import org.integratedmodelling.api.space.ISpatialExtent;
import org.integratedmodelling.collections.Path;
import org.integratedmodelling.common.metadata.Metadata;
import org.integratedmodelling.common.space.IGeometricShape;
import org.integratedmodelling.engine.geospace.Geospace;
import org.integratedmodelling.engine.geospace.extents.SpaceExtent;
import org.integratedmodelling.exceptions.KlabException;
import org.integratedmodelling.exceptions.KlabRuntimeException;
import org.integratedmodelling.exceptions.KlabValidationException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
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;
import com.vividsolutions.jts.geom.PrecisionModel;
import com.vividsolutions.jts.geom.prep.PreparedGeometry;
import com.vividsolutions.jts.geom.prep.PreparedGeometryFactory;
import com.vividsolutions.jts.io.WKBReader;
import com.vividsolutions.jts.io.WKBWriter;
import com.vividsolutions.jts.io.WKTReader;
import com.vividsolutions.jts.io.WKTWriter;
import com.vividsolutions.jts.operation.polygonize.Polygonizer;
import com.vividsolutions.jts.simplify.TopologyPreservingSimplifier;
/**
* TODO make geometry model configurable, static pointer. Integrate with the one in common
* package, just adding reprojection support.
*
* @author Ferdinando Villa
*/
public class ShapeValue implements
IGeometricShape,
IParseable,
ITopologicallyComparable,
IMetadataHolder {
PrecisionModel precisionModel = null;
CoordinateReferenceSystem crs = null;
IMetadata metadata = null;
Geometry value = null;
Type type = null;
static GeometryFactory gFactory = new GeometryFactory();
// these are used to speed up repeated point-in-polygon operations like
// those that RasterActivationLayer does.
private PreparedGeometry preparedShape;
private boolean preparationAttempted;
/**
* Return the world in lat/long coordinates.
*
* @return the world
*/
public static ShapeValue WORLD() {
return new ShapeValue(-180.0, -90.0, 180.0, 90.0, Geospace.get().getLatLonCRS());
}
public ShapeValue(String s) throws KlabValidationException {
parse(s);
}
public ShapeValue(Geometry geometry) {
this.value = geometry;
}
public ShapeValue(Geometry geometry, CoordinateReferenceSystem crs) {
this.value = geometry;
this.crs = crs;
}
@Override
public Class getGeometryClass() {
return value == null ? null : value.getClass();
}
/**
* Construct a rectangular "cell" from two points.
*
* @param x1
* @param y1
* @param x2
* @param y2
*/
public ShapeValue(double x1, double y1, double x2, double y2,
CoordinateReferenceSystem crs) {
this.value = makeCell(x1, y1, x2, y2);
this.crs = crs;
}
public ShapeValue(double x, double y, CoordinateReferenceSystem crs) {
this.value = gFactory.createPoint(new Coordinate(x, y));
this.crs = crs;
}
public ShapeValue(String s, CoordinateReferenceSystem crs)
throws KlabValidationException {
parse(s);
this.crs = crs;
}
/* create a polygon from the passed envelope */
public ShapeValue(ReferencedEnvelope e) {
GeometryFactory gFactory = new GeometryFactory();
Coordinate[] pts = {
new Coordinate(e.getMinX(), e.getMinY()),
new Coordinate(e.getMaxX(), e.getMinY()),
new Coordinate(e.getMaxX(), e.getMaxY()),
new Coordinate(e.getMinX(), e.getMaxY()),
new Coordinate(e.getMinX(), e.getMinY()) };
value = gFactory.createPolygon(gFactory.createLinearRing(pts), null);
crs = e.getCoordinateReferenceSystem();
}
public static ShapeValue sanitize(IShape shape) {
if (shape instanceof ShapeValue) {
return (ShapeValue) shape;
}
return new ShapeValue(((IGeometricShape) shape).getGeometry(), Geospace
.getCRSFromID("EPSG:"
+ ((IGeometricShape) shape).getSRID()));
}
public static Geometry makeCell(double x1, double y1, double x2, double y2) {
Coordinate[] pts = {
new Coordinate(x1, y1),
new Coordinate(x2, y1),
new Coordinate(x2, y2),
new Coordinate(x1, y2),
new Coordinate(x1, y1) };
return gFactory.createPolygon(gFactory.createLinearRing(pts), null);
}
public static Geometry makePoint(double x1, double y1) {
GeometryFactory gFactory = new GeometryFactory();
Coordinate coordinate = new Coordinate(x1, y1);
return gFactory.createPoint(coordinate);
}
public static Geometry makePoint(IGrid.Cell cell) {
double[] xy = cell.getCenter();
return gFactory.createPoint(new Coordinate(xy[0], xy[1]));
}
public void parse(String s) {
/*
* first see if we start with a token that matches "EPSG:[0-9]*". If so, set the
* CRS from it; otherwise it is null (not the plugin default).
*/
try {
if (s.startsWith("EPSG:") || s.startsWith("urn:")) {
int n = s.indexOf(' ');
String escode = s.substring(0, n);
s = s.substring(n + 1);
crs = Geospace.getCRSFromID(escode);
} else {
crs = Geospace.get().getDefaultCRS();
}
if (s.contains("(")) {
value = new WKTReader().read(s);
} else {
value = new WKBReader().read(WKBReader.hexToBytes(s));
}
} catch (Exception e) {
throw new KlabRuntimeException(e);
}
}
@Override
public String asText() {
return Geospace.getCRSIdentifier(getCRS(), true) + " "
+ (value.isEmpty() ? "GEOMETRYCOLLECTION EMPTY" : getWKB());
}
@Override
public String toString() {
String ret = new WKTWriter().write(value);
if (ret.endsWith(" EMPTY")) {
// for DBs that do not support any other empty WKT
ret = "GEOMETRYCOLLECTION EMPTY";
}
return ret;
}
/*
* (non-Javadoc)
*
* @see org.integratedmodelling.ima.core.value.Value#clone()
*/
@Override
public Object clone() {
return new ShapeValue((Geometry) (value.clone()));
}
public ShapeValue getBoundingBox() {
return new ShapeValue(value.getEnvelope(), crs);
}
public ShapeValue getCentroid() {
return new ShapeValue(value.getCentroid(), crs);
}
@Override
public Geometry getGeometry() {
return value;
}
public void simplify(double tolerance) {
value = TopologyPreservingSimplifier.simplify(value, tolerance);
}
public int getSRID(int def) {
int ret = value.getSRID();
if (ret <= 0)
ret = def;
return ret;
}
/**
* Get the referenced bounding box of the shape using the normalized axis order.
*
* @return the envelope
*/
public ReferencedEnvelope getEnvelope() {
return new ReferencedEnvelope(value.getEnvelopeInternal(), crs);
}
public CoordinateReferenceSystem getCRS() {
return crs;
}
@SuppressWarnings("unchecked")
public ShapeValue union(ShapeValue region) throws KlabException {
if ((crs != null || region.crs != null) && !crs.equals(region.crs))
region = region.transform(crs);
Geometry gg = value.union(region.value);
if ((value instanceof Polygon || value instanceof MultiPolygon)
&& !(gg instanceof Polygon || gg instanceof MultiPolygon)) {
Polygonizer p = new Polygonizer();
p.add(gg);
gg = value.getFactory()
.createMultiPolygon((Polygon[]) p.getPolygons().toArray(new Polygon[p
.getPolygons()
.size()]));
}
ShapeValue ret = new ShapeValue(gg);
ret.crs = crs;
return ret;
}
public ShapeValue intersection(ShapeValue region) throws KlabException {
if ((crs != null || region.crs != null) && !crs.equals(region.crs))
region = region.transform(crs);
ShapeValue ret = new ShapeValue(value.intersection(region.value));
ret.crs = crs;
return ret;
}
public ShapeValue difference(ShapeValue region) throws KlabException {
if ((crs != null || region.crs != null) && !crs.equals(region.crs))
region = region.transform(crs);
ShapeValue ret = new ShapeValue(value.difference(region.value));
ret.crs = crs;
return ret;
}
public String getWKT() {
return new WKTWriter().write(value);
}
public boolean isValid() {
return value == null ? true : value.isValid();
}
/**
* Return the area of the shape in square meters. Transforms the shape if necessary
* and computes the area, so it may be expensive. The shape must have a CRS.
*
* @return the area in square meters, using projection EPSG:3005
*/
@Override
public double getArea() {
if (crs == null)
throw new KlabRuntimeException("shape: cannot compute area of shape without CRS");
double ret = 0.0;
try {
ret = JTS
.transform(value, CRS
.findMathTransform(crs, Geospace.get().getMetersCRS()))
.getArea();
} catch (Exception e) {
throw new KlabRuntimeException(e);
}
return ret;
}
/**
* Return a new ShapeValue transformed to the passed CRS. Must have a crs.
*
* @param ocrs the CRS to transform to.
* @return a new shapevalue
* @throws KlabException if we have no CRS or a transformation cannot be found.
*/
public ShapeValue transform(CoordinateReferenceSystem ocrs) throws KlabException {
if (crs == null)
throw new KlabValidationException("shape: cannot transform shape without CRS");
if (ocrs.equals(this.crs))
return this;
Geometry g = null;
try {
g = JTS.transform(value, CRS.findMathTransform(crs, ocrs));
} catch (Exception e) {
throw new KlabValidationException(e);
}
return new ShapeValue(g, ocrs);
}
public String getWKB() {
return new String(WKBWriter.bytesToHex(new WKBWriter().write(value)));
}
public ShapeValue convertToMeters() throws KlabException {
return transform(Geospace.get().getMetersCRS());
}
@Override
public boolean contains(ShapeValue o) throws KlabException {
return value.contains(o.transform(crs).value);
}
@Override
public boolean overlaps(ShapeValue o) throws KlabException {
return value.overlaps(o.transform(crs).value);
}
@Override
public boolean intersects(ShapeValue o) throws KlabException {
return value.intersects(o.transform(crs).value);
}
@Override
public IMetadata getMetadata() {
if (metadata == null)
metadata = new Metadata();
return metadata;
}
@Override
public ITopologicallyComparable union(ITopologicallyComparable other)
throws KlabException {
return union((ShapeValue) other);
}
@Override
public ITopologicallyComparable intersection(ITopologicallyComparable other)
throws KlabException {
return intersection((ShapeValue) other);
}
@Override
public double getCoveredExtent() {
return getArea();
}
@Override
public Geometry getStandardizedGeometry() {
try {
return transform(Geospace.get().getDefaultCRS()).getGeometry();
} catch (KlabException e) {
throw new KlabRuntimeException(e);
}
}
public ISpatialExtent asExtent() {
return new SpaceExtent(this);
}
@Override
public Type getGeometryType() {
if (type == null) {
if (value instanceof Polygon) {
type = Type.POLYGON;
} else if (value instanceof MultiPolygon) {
type = Type.MULTIPOLYGON;
} else if (value instanceof Point) {
type = Type.POINT;
} else if (value instanceof MultiLineString) {
type = Type.MULTILINESTRING;
} else if (value instanceof LineString) {
type = Type.LINESTRING;
} else if (value instanceof MultiPoint) {
type = Type.MULTIPOINT;
}
}
return type;
}
@Override
public int getSRID() {
return Integer.parseInt(Path.getLast(Geospace.getCRSIdentifier(crs, true), ':'));
}
public boolean containsCoordinates(double x, double y) {
checkPreparedShape();
return preparedShape == null
? value.contains(gFactory.createPoint(new Coordinate(x, y)))
: preparedShape.contains(gFactory.createPoint(new Coordinate(x, y)));
}
private void checkPreparedShape() {
if (this.preparedShape == null && !preparationAttempted) {
preparationAttempted = true;
try {
this.preparedShape = PreparedGeometryFactory.prepare(value);
} catch (Throwable t) {
//
}
}
}
public PreparedGeometry getPreparedGeometry() {
checkPreparedShape();
return preparedShape;
}
public double getCoverage(Cell cell, boolean simpleIntersection) {
checkPreparedShape();
if (preparedShape == null) {
if (simpleIntersection) {
Geometry gm = makePoint(cell);
return gm.intersects(value) ? 1.0 : 0.0;
}
Geometry gm = makeCell(cell.getMinX(), cell.getMinY(), cell.getMaxX(), cell
.getMaxY());
return gm.covers(value) ? 1.0
: (gm.intersection(value).getArea() / gm.getArea());
}
if (simpleIntersection) {
return preparedShape.covers(makePoint(cell)) ? 1 : 0;
}
Geometry gm = makeCell(cell.getMinX(), cell.getMinY(), cell.getMaxX(), cell
.getMaxY());
return preparedShape.covers(gm) ? 1.0
: (gm.intersection(value).getArea() / gm.getArea());
}
@Override
public boolean isEmpty() {
return value.isEmpty();
}
/**
* If the shape is a point or a polygon with witdh or height smaller than the passed
* minimum size, expand to a square polygon with the given side.
*
* @param d
* @return
*/
public ShapeValue expandIfPoint(double d) {
ShapeValue ret = this;
if (this.getGeometryType() == Type.POINT || this.value.getArea() < (d * d)) {
double delta = d / 2;
ReferencedEnvelope env = getEnvelope();
double x = 0, y = 0;
if (env.getHeight() == 0 || env.getWidth() == 0) {
// use min x,y - the centroid will be NaN
x = env.getMinX();
y = env.getMinY();
} else {
Point centroid = this.value.getCentroid();
x = centroid.getX();
y = centroid.getY();
}
ReferencedEnvelope envelope = new ReferencedEnvelope(x - delta, x + delta, y - delta, y
+ delta, crs);
ret = new ShapeValue(envelope);
}
return ret;
}
}