Envelope

Envelopes are used to represent the bounds of a geometry, and are used very frequently as a way to quickly check if a geometry is in the area you are interested in.

../../_images/envelope.PNG

GeoTools, through virtue of reusing code, has two “Envelope” implementations to contend with:

  • Java Rectangle2D - we have GeneralBounds which is spatial extension of the Java Rectangle2D class. Our implementation makes use of doubles to store coordinates and holds on to a CoordinateReferenceSystem allowing us to tell where the coordinates are located.

  • JTS Envelope - we have ReferencedEnvelope which is adds a CoordinateReferenceSystem to a traditional JTS Envelope. A subclass of ReferencedEnvelope is ReferencedEnvelope3D which supports a third dimension on top of the regular two dimensions.

As you can see ReferencedEnvelope and ReferencedEnvelope3D are a bit of a olive branch between the JTS Geometry model and ISO Geometry model.

You will find other “Rectangles” around as you make use of GeoTools in a real world application.

  • Java Rectangles

    Java Rectangles record x,y,w,h:

  • Rectangle2D

    • Rectangle2D.Double rectangle working with doubles

    • Rectangle2D.Float rectangle working with floats

  • GeneralBounds we have a spatial specific version of Rectangle2D that implements ISO Geometry Envelope

  • Rectangle the original rectangle for working on the screen, measured in integer pixels.

ReferencedEnvelope

ReferencedEnvelope is all of these:

  • org.locationtech.jts.geom.Envelope - as defined by the JTS Topology System ( a Simple Feature for SQL concept)

  • org.geotools.api.geometry.BoundingBox - 2D bounds as defined by the ISO 19107 Geometry

  • org.geotools.api.geometry.Envelope - captures 3D bounds as defined by ISO 19107 Geometry.

Note that in order to support 3D bounds (and use a 3D Coordinate Reference System) we must create an instance of the child class ReferencedEnvelope3D (see below).

In short this is the class to use when you want to represent a bounds in GeoTools. The only other thing of note is the that the constructor expects the input in xMin,xMax,yMin,yMax order and expects a (2D) CRS:

        ReferencedEnvelope envelope =
                new ReferencedEnvelope(0, 10, 0, 20, DefaultGeographicCRS.WGS84);

        double xMin = envelope.getMinX();
        double yMin = envelope.getMinY();

        double xMax = envelope.getMaxX();
        double yMax = envelope.getMaxY();

        double width = envelope.getWidth();
        double height = envelope.getHeight();

        double xCenter = envelope.getMedian(0);
        double yCenter = envelope.getMedian(1);

        CoordinateReferenceSystem crs = envelope.getCoordinateReferenceSystem();
        int dimension = envelope.getDimension();

        // Direct access to internal upper and lower positions
        Position lower = envelope.getLowerCorner();
        Position upper = envelope.getUpperCorner();

        // expand to include 15, 30
        envelope.include(15, 30);

        envelope.isEmpty(); // check if storing width and height are 0

        envelope.isNull(); // check if "null" (not storing anything)
        envelope.setToNull();

ReferencedEnvelope does one thing very well; it is an Envelope that has a CoordinateReferenceSystem. Because it has a CoordinateReferenceSystem you can quickly transform it between projections.:

        CoordinateReferenceSystem sourceCRS = CRS.decode("EPSG:4326");
        ReferencedEnvelope envelope = new ReferencedEnvelope(0, 10, 0, 20, sourceCRS);

        // Transform using 10 sample points around the envelope
        CoordinateReferenceSystem targetCRS = CRS.decode("EPSG:23032");
        ReferencedEnvelope result = envelope.transform(targetCRS, true, 10);

ReferencedEnvelope is used in a lot of GeoTools interfaces, basically anywhere we can get away with it. Using a raw JTS Envelope without knowing the CoordinateReferenceSystem is difficult to use as the information is incomplete forcing client code to make assumptions. Some code assumes the envelope is in WGS84 while other code assumes it is in the same Coordinate Reference System as the data being worked on.

Some of our older interfaces that you are forced to read the javadocs in order to figure out the CoordinateReferenceSystem for a returned Envelope.

  • Using a FeatureSource without ReferencedEnvelope example:

    Envelope bounds = featureSource.getBounds();
    
    CoordinateReferenceSystem crs = featureSource.getSchema().getDefaultGeometry().getCoordinateSystem();
    
  • Using a FeatureSource with ReferencedEnvelope:

    ReferencedEnvelope bounds = (ReferencedEnvelope) featureSource.getBounds();
    
    CoordinateReferenceSystem crs = bounds.getCoordinateReferenceSystem();
    

ReferencedEnvelope3D

ReferencedEnvelope3D is all of these:

  • ReferencedEnvelope including all parent classes and interfaces

  • org.geotools.api.geometry.BoundingBox3D - 3D bounds as defined by the ISO 19107 Geometry

This is the class to use when you want to represent a 3D bounds in GeoTools. The constructor expects the input in xMin,xMax,yMin,yMax,zMin,zMax order and expects a 3D CRS:

        ReferencedEnvelope3D envelope =
                new ReferencedEnvelope3D(0, 10, 0, 20, 0, 30, DefaultGeographicCRS.WGS84_3D);

        double xMin = envelope.getMinX();
        double yMin = envelope.getMinY();
        double zMin = envelope.getMinZ();

        double xMax = envelope.getMaxX();
        double yMax = envelope.getMaxY();
        double zMax = envelope.getMaxZ();

        double width = envelope.getWidth();
        double height = envelope.getHeight();
        double depth = envelope.getDepth();

        double xCenter = envelope.getMedian(0);
        double yCenter = envelope.getMedian(1);
        double zCenter = envelope.getMedian(2);

        CoordinateReferenceSystem crs = envelope.getCoordinateReferenceSystem();
        int dimension = envelope.getDimension();

        // Direct access to internal upper and lower positions
        Position lower = envelope.getLowerCorner();
        Position upper = envelope.getUpperCorner();

        // expand to include 15, 30, 40
        envelope.include(15, 30, 40);

        envelope.isEmpty(); // check if storing width and height are 0

        envelope.isNull(); // check if "null" (not storing anything)
        envelope.setToNull();

As explained above, when using a 3D CRS we must create an instance of ReferencedEnvelope3D and not of its parent class. If we are not sure what dimension we are dealing with, there are safe ways to create, copy, convert or reference ReferencedEnvelope instances:

        ReferencedEnvelope
                env; // can hold both regular ReferencedEnvelope as well as ReferencedEnvelope3D
        ReferencedEnvelope original = null; // can be instance of ReferencedEnvelope3D;
        CoordinateReferenceSystem crs = null; // can be 2D or 3D
        Bounds opengis_env = null; // can be instance of ReferencedEnvelope(3D)
        org.locationtech.jts.geom.Envelope jts_env =
                null; // can be instance of ReferencedEnvelope(3D)
        BoundingBox bbox = null; // can be instance of ReferencedEnvelope(3D)

        // safely copy ReferencedEnvelope, uses type of original to determine type
        env = ReferencedEnvelope.create(original);

        // safely create ReferencedEnvelope from CRS, uses dimension to determine type
        env = ReferencedEnvelope.create(crs);

        // safely create ReferencedEnvelope from org.geotools.api.geometry.Envelope, uses dimension
        // in
        // Envelope to determine type
        env = ReferencedEnvelope.create(opengis_env, crs);

        // safely create ReferencedEnvelope from org.locationtech.jts.geom.Envelope, uses
        // dimension in Envelope to determine type
        env = ReferencedEnvelope.envelope(jts_env, crs);

        // safely reference org.geotools.api.geometry.Envelope as ReferencedEnvelope
        // --> if it is a ReferencedEnvelope(3D), simply cast it; if not, create a conversion
        env = ReferencedEnvelope.reference(opengis_env);

        // safely reference org.locationtech.jts.geom.Envelope as ReferencedEnvelope
        // --> if it is a ReferencedEnvelope(3D), simply cast it; if not, create a conversion
        env = ReferencedEnvelope.reference(jts_env);

        // safely reference BoundingBox as ReferencedEnvelope
        // --> if it is a ReferencedEnvelope(3D), simply cast it; if not, create a conversion
        env = ReferencedEnvelope.reference(bbox);

OpenGIS Envelope

OpenGIS records a “rectangle” as a bounds along the axis mentioned by the CoordinateReferenceSystem object. You can use this idea to record a simple rectangle in space, a height range and a range in time as needed.

../../_images/envelope2.PNG
  • GeneralBounds - was introduced above; used to bridge to Java2D

  • ReferencedEnvelope - was introduced above; used to bridge to JTS Geometry

  • GeneralEnvelope - allows you to record spans in multiple dimensions (think depth, height or time)

Since Envelope is just and interface, so we will use ReferencedEnvelope for the example:

        CoordinateReferenceSystem wsg84 = CRS.decode("EPSG:4326");
        Bounds envelope = new ReferencedEnvelope(0, 10, 0, 20, wsg84);

        double xMin = envelope.getMinimum(0);
        double yMin = envelope.getMinimum(1);

        double xMax = envelope.getMaximum(0);
        double yMax = envelope.getMaximum(1);

        double width = envelope.getSpan(0);
        double height = envelope.getSpan(1);

        double xCenter = envelope.getMedian(0);
        double yCenter = envelope.getMedian(1);

        CoordinateReferenceSystem crs = envelope.getCoordinateReferenceSystem();

        // Direct access to internal upper and lower positions
        Position lower = envelope.getLowerCorner();
        Position upper = envelope.getUpperCorner();

        // expand to include 15, 30
        upper.setOrdinate(0, Math.max(upper.getOrdinate(0), 15));
        upper.setOrdinate(1, Math.max(upper.getOrdinate(1), 30));
        lower.setOrdinate(0, Math.min(lower.getOrdinate(0), 15));
        lower.setOrdinate(1, Math.min(lower.getOrdinate(1), 30));

You can see even in a simple example we should be looking at the CRS to figure out what the axis is actually measuring.

If you are super confident that you are working with data in X/Y order you can directly make use of BoundingBox box. BoundingBox is an extension of Envelope for working with 2D data, and it has been made method compatible with JTS Envelope where possible.

Since BoundingBox is just an interface, so we will use ReferencedEnvelope for this example:

        CoordinateReferenceSystem wsg84 = CRS.decode("EPSG:4326");
        org.geotools.api.geometry.BoundingBox bbox = new ReferencedEnvelope(0, 10, 0, 20, wsg84);

        double xMin = bbox.getMinX();
        double yMin = bbox.getMinY();

        double xMax = bbox.getMaxX();
        double yMax = bbox.getMaxY();

        double width = bbox.getWidth();
        double height = bbox.getHeight();

        double xCenter = bbox.getMedian(0);
        double yCenter = bbox.getMedian(1);

        CoordinateReferenceSystem crs = bbox.getCoordinateReferenceSystem();

        // Direct access to internal upper and lower positions
        Position lower = bbox.getLowerCorner();
        Position upper = bbox.getUpperCorner();

        // expand to include 15, 30
        bbox.include(15, 30);

JTS Envelope

The JTS Topology Suite has the concept of an Envelope recorded in x1,x2, y1,y2 order.

You can see that the use of JTS Envelope has the same “assumptions” as the use of BoundingBox above.:

        org.locationtech.jts.geom.Envelope envelope = new Envelope(0, 10, 0, 20);
        double xMin = envelope.getMinX();
        double yMin = envelope.getMinY();

        double xMax = envelope.getMaxX();
        double yMax = envelope.getMaxY();

        double width = envelope.getWidth(); // assuming axis 0 is easting
        double height = envelope.getHeight(); // assuming axis 1 is nothing

        // Expand an existing envelope
        Envelope bbox = new Envelope();
        envelope.expandToInclude(bbox);

        // Use
        envelope.covers(5, 10); // inside or on edge!
        envelope.contains(5, 10); // inside only

        // Null
        envelope.isNull(); // check if "null" (not storing anything)
        envelope.setToNull();

Transform an Envelope using the JTS Utility class:

        CoordinateReferenceSystem sourceCRS = CRS.decode("EPSG:4326");
        CoordinateReferenceSystem targetCRS = CRS.decode("EPSG:23032");

        Envelope envelope = new Envelope(0, 10, 0, 20);

        MathTransform transform = CRS.findMathTransform(sourceCRS, targetCRS);

        Envelope quick = JTS.transform(envelope, transform);

        // Sample 10 points around the envelope
        Envelope better = JTS.transform(envelope, null, transform, 10);