All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy