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

edu.ucr.cs.bdlab.beast.geolite.EnvelopeNDLite Maven / Gradle / Ivy

/*
 * Copyright 2020 University of California, Riverside
 *
 * 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 edu.ucr.cs.bdlab.beast.geolite;

import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.CoordinateSequence;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryCollection;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Polygon;

import java.io.Serializable;
import java.util.Arrays;

/**
 * A light-weight class that stores an n-dimensional envelope without geometry information
 */
public class EnvelopeNDLite implements Serializable {
  /**The coordinate of the minimum corner*/
  private double[] minCoord;

  /**The coordinate of the maximum corner*/
  private double[] maxCoord;

  public EnvelopeNDLite() {
  }

  /**
   * Create an envelope from a list of coordinates. The passed arguments is a list in the form (x1, y1, z1, ...,
   * x2, y1, z2, ...)
   * @param numDimensions the number of dimensions to initialize to
   * @param args (optional) the coordinates to initialize to. If not set, the envelope is initialized to an
   *             inverse infinite envelope that has the range (+∞, -∞) in all dimensions.
   */
  public EnvelopeNDLite(int numDimensions, double ... args) {
    this.setCoordinateDimension(numDimensions);
    if (args.length == 0)
      return;
    if (args.length != numDimensions * 2)
      throw new RuntimeException("The arguments should contain 2 * numDimensions numbers");
    for (int d = 0; d < numDimensions; d++) {
      this.minCoord[d] = args[d];
      this.maxCoord[d] = args[numDimensions + d];
    }
  }

  /**
   * Creates an envelope initialized to the given two corners.
   * @param minCoord the coordinate of the lower corner (on all dimensions and inclusive)
   * @param maxCoord the coordinate of the upper corner (on all dimensions and exclusive)
   */
  public EnvelopeNDLite(double[] minCoord, double[] maxCoord) {
    assert minCoord.length == maxCoord.length;
    this.minCoord = Arrays.copyOf(minCoord, minCoord.length);
    this.maxCoord = Arrays.copyOf(maxCoord, maxCoord.length);
  }

  public EnvelopeNDLite(EnvelopeNDLite other) {
    this.minCoord = Arrays.copyOf(other.minCoord, other.minCoord.length);
    this.maxCoord = Arrays.copyOf(other.maxCoord, other.maxCoord.length);
  }

  /**
   * Construct from a geometry envelope
   * @param mbr a geometry envelope to initialize from
   */
  public EnvelopeNDLite(EnvelopeND mbr) {
    this.set(mbr);
  }

  public EnvelopeNDLite(Envelope e) {
    this.setCoordinateDimension(2);
    this.setMinCoord(0, e.getMinX());
    this.setMinCoord(1, e.getMinY());
    this.setMaxCoord(0, e.getMaxX());
    this.setMaxCoord(1, e.getMaxY());
  }

  /**
   * Sets the number of dimensions for the envelope. If the number of dimensions is different from the current one,
   * the envelope is updated to be an empty envelope, i.e., all minimum and maximum coordinates are set to
   * ∞ and -∞, respectively.
   * @param numDimensions the new number of dimensions
   */
  public void setCoordinateDimension(int numDimensions) {
    if (this.minCoord == null || numDimensions != this.getCoordinateDimension()) {
      this.minCoord = new double[numDimensions];
      this.maxCoord = new double[numDimensions];
      // Set to an infinite reversed envelope to mark it as empty in all dimensions
      Arrays.fill(this.minCoord, Double.POSITIVE_INFINITY);
      Arrays.fill(this.maxCoord, Double.NEGATIVE_INFINITY);
    }
  }

  /**
   * Returns the number of dimensions for a coordinate in this envelope.
   * @return the number of dimensions of this envelope
   */
  public final int getCoordinateDimension() {
    return minCoord == null ? 0 : minCoord.length;
  }

  /**
   * Returns the minimum coordinate of dimension d
   * @param d the dimension
   * @return the minimum coordinate of the given dimension
   */
  public final double getMinCoord(int d) {
    return minCoord[d];
  }

  /**
   * Sets the minimum coordinate of one dimension
   * @param d the coordinate
   * @param x the new minimum dimension
   */
  public final void setMinCoord(int d, double x) {
    minCoord[d] = x;
    // Invalidate the envelope
  }

  /**
   * Returns the maximum coordinate of dimension d
   * @param d the dimension
   * @return the maximum coordinate of the given dimension
   */
  public final double getMaxCoord(int d) {
    return maxCoord[d];
  }

  /**
   * Sets the maximum coordinate of one dimension
   * @param d the coordinate
   * @param x the new maximum dimension
   */
  public final void setMaxCoord(int d, double x) {
    maxCoord[d] = x;
  }

  public void set(EnvelopeNDLite other) {
    if (other.isEmpty())
      this.setEmpty();
    else {
      this.setCoordinateDimension(other.getCoordinateDimension());
      for (int $d = 0; $d < this.getCoordinateDimension(); $d++) {
        this.setMinCoord($d, other.getMinCoord($d));
        this.setMaxCoord($d, other.getMaxCoord($d));
      }
    }
  }

  public void set(EnvelopeND other) {
    if (other.isEmpty())
      this.setEmpty();
    else {
      this.setCoordinateDimension(other.getCoordinateDimension());
      for (int $d = 0; $d < this.getCoordinateDimension(); $d++) {
        this.setMinCoord($d, other.getMinCoord($d));
        this.setMaxCoord($d, other.getMaxCoord($d));
      }
    }
  }


  public void set(PointND p) {
    this.setCoordinateDimension(p.getCoordinateDimension());
    for (int $d = 0; $d < p.getCoordinateDimension(); $d++) {
      this.setMinCoord($d, p.getCoordinate($d));
      this.setMaxCoord($d, p.getCoordinate($d));
    }
  }

  public void set(Coordinate c) {
    this.setCoordinateDimension(2);
    this.setMinCoord(0, c.getX());
    this.setMaxCoord(0, c.getX());
    this.setMinCoord(1, c.getY());
    this.setMaxCoord(1, c.getY());
  }

  /**
   * Sets this envelope to cover the entire space (-∞, +∞)
   */
  public void setInfinite() {
    for (int d = 0; d < getCoordinateDimension(); d++) {
      this.setMinCoord(d, Double.NEGATIVE_INFINITY);
      this.setMaxCoord(d, Double.POSITIVE_INFINITY);
    }
  }

  /**
   * Returns the side length of dimension {@code d}.
   * @param d the index of the dimension
   * @return the side length along the given dimension
   */
  public double getSideLength(int d) {
    return this.getMaxCoord(d) - this.getMinCoord(d);
  }

  /**
   * Returns the center coordinate along the dimension {@code d}
   * @param d the index of the dimension
   * @return the center along the given dimension
   */
  public double getCenter(int d) {
    return (this.getMinCoord(d) + this.getMaxCoord(d)) / 2.0;
  }

  /**
   * Expands this envelope to enclose the given point coordinate. The given point should have the same coordinate
   * dimension of the envelope.
   * @param point the point to include in this envelope
   * @return this geometry so that it can be merged serially
   */
  public EnvelopeNDLite merge(double[] point) {
    assert point.length == getCoordinateDimension();
    for (int d = 0; d < getCoordinateDimension(); d++) {
      this.setMinCoord(d, Math.min(this.getMinCoord(d), point[d]));
      this.setMaxCoord(d, Math.max(this.getMaxCoord(d), point[d]));
    }
    return this;
  }

  /**
   * Expands this envelope to enclose the given geometry.
   * @param geom the geometry to include in this envelope
   * @return this envelope so that it can be merged serially
   */
  public EnvelopeNDLite merge(Geometry geom) {
    if (geom == null || geom.isEmpty())
      return this;
    if (geom instanceof EnvelopeND) {
      EnvelopeND env = (EnvelopeND) geom;
      this.setCoordinateDimension(env.envelopeLite.getCoordinateDimension());
      for (int d = 0; d < getCoordinateDimension(); d++) {
        this.setMinCoord(d, Math.min(this.getMinCoord(d), env.envelopeLite.getMinCoord(d)));
        this.setMaxCoord(d, Math.max(this.getMaxCoord(d), env.envelopeLite.getMaxCoord(d)));
      }
    } else if (geom instanceof PointND) {
      PointND pt = (PointND) geom;
      this.setCoordinateDimension(pt.getCoordinateDimension());
      for (int d = 0; d < this.getCoordinateDimension(); d++) {
        this.setMinCoord(d, Math.min(this.getMinCoord(d), pt.getCoordinate(d)));
        this.setMaxCoord(d, Math.max(this.getMaxCoord(d), pt.getCoordinate(d)));
      }
    } else if (geom instanceof Point) {
      merge(((org.locationtech.jts.geom.Point) geom).getCoordinateSequence());
    } else if (geom instanceof LineString) {
      merge(((LineString)geom).getCoordinateSequence());
    } else if (geom instanceof Polygon) {
      Polygon p = (Polygon) geom;
      merge(p.getExteriorRing());
      for (int $i = 0; $i < p.getNumInteriorRing(); $i++)
        merge(p.getInteriorRingN($i));
    } else if (geom instanceof GeometryCollection) {
      for (int $i = 0; $i < geom.getNumGeometries(); $i++)
        merge(geom.getGeometryN($i));
    } else {
      throw new RuntimeException("Unsupported geometry type "+geom.getClass());
    }
    return this;
  }

  public EnvelopeNDLite merge(EnvelopeNDLite other) {
    assert this.getCoordinateDimension() == other.getCoordinateDimension() : "Mismatching number of dimensions";
    for (int $d = 0; $d < this.getCoordinateDimension(); $d++) {
      this.setMinCoord($d, Math.min(this.getMinCoord($d), other.getMinCoord($d)));
      this.setMaxCoord($d, Math.max(this.getMaxCoord($d), other.getMaxCoord($d)));
    }
    return this;
  }

  public void merge(CoordinateSequence cs) {
    // TODO should we take Z and M into account?
    int dimension = 2;
    this.setCoordinateDimension(dimension);
    for (int $i = 0; $i < cs.size(); $i++) {
      for (int $d = 0; $d < dimension; $d++) {
        double v = cs.getOrdinate($i, $d);
        this.setMinCoord($d, Math.min(this.getMinCoord($d), v));
        this.setMaxCoord($d, Math.max(this.getMaxCoord($d), v));
      }
    }
  }

  /**
   * Adjusts the coordinate dimensions for this envelope and updates its boundaries to the given two corners.
   * It is assumed that minCoords[i] ≤ maxCoords[i] for all i = 0, ..., d-1. No sanity check is done.
   * @param minCoords the coordinates of the new lower corner
   * @param maxCoords the coordinates of the new upper corner
   */
  public void set(double[] minCoords, double[] maxCoords) {
    assert minCoords.length == maxCoords.length:
        String.format("Mismatching number of dimensions for minimum and maximum coordinates (%d != %d)",
            minCoords.length, maxCoords.length);
    this.setCoordinateDimension(minCoords.length);
    for (int $d = 0; $d < minCoords.length; $d++) {
      this.setMinCoord($d, minCoords[$d]);
      this.setMaxCoord($d, maxCoords[$d]);
    }
  }

  public EnvelopeNDLite intersectionEnvelope(EnvelopeNDLite other) {
    if (this.getCoordinateDimension() != other.getCoordinateDimension())
      throw new RuntimeException(String.format("Incompatible number of dimensions %d != %d",
          this.getCoordinateDimension(), other.getCoordinateDimension()));
    EnvelopeNDLite result = new EnvelopeNDLite(this.getCoordinateDimension());
    for (int d = 0; d < this.getCoordinateDimension(); d++) {
      result.minCoord[d] = Math.max(this.minCoord[d], other.minCoord[d]);
      result.maxCoord[d] = Math.min(this.maxCoord[d], other.maxCoord[d]);
    }
    return result;
  }

  /**
   * Checks if this envelope overlaps the envelope defined by the given two coordinates.
   * @param min the lower corner of the box to test
   * @param max the upper corner of the box to test
   * @return {@code true} if the interior of the two boxes (envelopes) are not disjoint
   */
  public boolean overlaps(double[] min, double[] max) {
    if (min.length != getCoordinateDimension())
      throw new RuntimeException(String.format("The dimension of the coordinates are incompatible. Expected %d found %d!",
          getCoordinateDimension(), min.length));
    for (int d = 0; d < getCoordinateDimension(); d++) {
      if (max[d] <= this.getMinCoord(d) || this.getMaxCoord(d) <= min[d])
        return false;
    }
    return true;
  }

  /**
   * Similar to {@link #overlaps(double[], double[])}
   * @param envelope2 the envelope to test for intersection
   * @return {@code true} if the interior of this and the given envelopes are not disjoint
   */
  public boolean intersectsEnvelope(EnvelopeNDLite envelope2) {
    for (int d = 0; d < getCoordinateDimension(); d++) {
      // Special case to address an envelope that represents a point at the lower edge of the other envelope
      if (envelope2.getMinCoord(d) != this.getMinCoord(d) &&
          (envelope2.getMaxCoord(d) <= this.getMinCoord(d) || this.getMaxCoord(d) <= envelope2.getMinCoord(d)))
        return false;
    }
    return true;
  }

  public boolean intersectsEnvelope(Envelope envelope2) {
    if (envelope2.getMaxX() <= this.getMinCoord(0) || this.getMaxCoord(0) <= envelope2.getMinX())
      return false;
    if (envelope2.getMaxY() <= this.getMinCoord(1) || this.getMaxCoord(1) <= envelope2.getMinY())
      return false;
    return true;
  }

  public boolean containsPoint(double[] coord) {
    assert coord.length == getCoordinateDimension();
    for (int d = 0; d < coord.length; d++)
      if (coord[d] < getMinCoord(d) || coord[d] >= getMaxCoord(d))
        return false;
    return true;
  }

  public boolean containsPoint(Coordinate c) {
    return c.getX() >= this.getMinCoord(0) && c.getX() < this.getMaxCoord(0) &&
        c.getY() >= this.getMinCoord(1) && c.getY() < this.getMaxCoord(1);
  }

  public boolean containsPoint(PointND p) {
    assert p.getCoordinateDimension() == getCoordinateDimension();
    for (int d = 0; d < p.getCoordinateDimension(); d++)
      if (p.getCoordinate(d) < this.getMinCoord(d) || p.getCoordinate(d) >= this.getMaxCoord(d))
        return false;
    return true;
  }

  public boolean isEmpty() {
    if (getCoordinateDimension() == 0)
      return true;
    for (int d = 0; d < getCoordinateDimension(); d++)
      if (getMaxCoord(d) < getMinCoord(d) || Double.isNaN(getMinCoord(d)) || Double.isNaN(getMaxCoord(d)))
        return true;
    return false;
  }

  public void setEmpty() {
    for (int d = 0; d < getCoordinateDimension(); d++) {
      this.setMinCoord(d, Double.POSITIVE_INFINITY);
      this.setMaxCoord(d, Double.NEGATIVE_INFINITY);
    }
  }

  /**
   * Shrink this envelope to be fully contained in the given envelope. In other words, the result of this function
   * is the intersection of this envelope with the given envelope.
   * @param that the other envelope to shrink this envelope to.
   */
  public void shrink(EnvelopeNDLite that) {
    assert this.getCoordinateDimension() == that.getCoordinateDimension();
    for (int d = 0; d < this.getCoordinateDimension(); d++) {
      this.setMinCoord(d, Math.max(this.getMinCoord(d), that.getMinCoord(d)));
      this.setMaxCoord(d, Math.min(this.getMaxCoord(d), that.getMaxCoord(d)));
    }
  }

  /**
   * Expand the envelope in all directions by the given buffer. The buffer size can be negative to shrink the envelope.
   * If only one value is given, it is used to expand/shrink all dimensions.
   * @param delta the size of the buffer to expand in all dimensions.
   */
  public void buffer(double ... delta) {
    for (int d = 0; d < this.getCoordinateDimension(); d++) {
      this.setMinCoord(d, this.getMinCoord(d) - delta[d % delta.length]);
      this.setMaxCoord(d, this.getMaxCoord(d) + delta[d % delta.length]);
    }
  }

  /**
   * Returns {@code true} if this envelope contains the given envelope.
   * @param other the other envelope to test for containment
   * @return {@code ture} if the the other envelope is completed contained in this envelope.
   */
  public boolean containsEnvelope(EnvelopeNDLite other) {
    for (int d = 0; d < this.getCoordinateDimension(); d++) {
      if (other.getMinCoord(d) < this.getMinCoord(d) || other.getMaxCoord(d) > this.getMaxCoord(d))
        return false;
    }
    return true;
  }

  public boolean containsEnvelope(Envelope other) {
    if (other.getMinX() < this.getMinCoord(0) || other.getMaxX() > this.getMaxCoord(0))
      return false;
    if (other.getMinY() < this.getMinCoord(1) || other.getMaxY() > this.getMaxCoord(1))
      return false;
    return true;
  }

  /**
   * Checks if this envelope is infinite in all dimensions.
   * @return {@code true} if this envelope is not infinite in all dimensions
   */
  public boolean isFinite() {
    for (int d = 0; d < getCoordinateDimension(); d++) {
      if (Double.isFinite(getMinCoord(d)) || Double.isFinite(getMaxCoord(d)))
        return true;
    }
    return false;
  }

  public boolean equalsExact(EnvelopeNDLite other) {
    if (this.getCoordinateDimension() != other.getCoordinateDimension())
      return false;
    for (int $d = 0; $d < this.getCoordinateDimension(); $d++) {
      if (this.getMinCoord($d) != other.getMinCoord($d) || this.getMaxCoord($d) != other.getMaxCoord($d))
        return false;
    }
    return true;
  }

  public boolean equalsExact(EnvelopeNDLite other, double tolerance) {
    if (this.getCoordinateDimension() != other.getCoordinateDimension())
      return false;
    for (int $d = 0; $d < this.getCoordinateDimension(); $d++) {
      if (Math.abs(this.getMinCoord($d) - other.getMinCoord($d)) > tolerance)
        return false;
      if (Math.abs(this.maxCoord[$d] - other.getMaxCoord($d)) > tolerance)
        return false;
    }
    return true;
  }

  public StringBuilder toWKT(StringBuilder out) {
    if (this.getCoordinateDimension() != 2)
      throw new RuntimeException("WKT not supported for higher-dimension polygons");
    out.append("POLYGON((");
    out.append(this.minCoord[0]);
    out.append(' ');
    out.append(this.minCoord[1]);
    out.append(',');

    out.append(this.maxCoord[0]);
    out.append(' ');
    out.append(this.minCoord[1]);
    out.append(',');

    out.append(this.maxCoord[0]);
    out.append(' ');
    out.append(this.maxCoord[1]);
    out.append(',');

    out.append(this.minCoord[0]);
    out.append(' ');
    out.append(this.maxCoord[1]);
    out.append(',');

    out.append(this.minCoord[0]);
    out.append(' ');
    out.append(this.minCoord[1]);
    out.append(')'); // The ring
    out.append(')'); // The polygon
    return out;
  }

  @Override
  public String toString() {
    StringBuilder str = new StringBuilder();
    str.append("Envelope (");
    if (isEmpty()) {
      str.append("EMPTY");
    } else {
      str.append('[');
      for (int d = 0; d < getCoordinateDimension(); d++) {
        if (d > 0) {
          str.append(',');
          str.append(' ');
        }
        str.append(getMinCoord(d));
      }
      str.append("]->[");
      for (int d = 0; d < getCoordinateDimension(); d++) {
        if (d > 0) {
          str.append(',');
          str.append(' ');
        }
        str.append(getMaxCoord(d));
      }
      str.append("]");
    }
    str.append(")");
    return str.toString();
  }

  public String encodeAsString() {
    StringBuilder str = new StringBuilder();
    for (int $d = 0; $d < this.getCoordinateDimension(); $d++) {
      str.append(getMinCoord($d));
      str.append(',');
    }
    for (int $d = 0; $d < this.getCoordinateDimension(); $d++) {
      str.append(getMaxCoord($d));
      if ($d < this.getCoordinateDimension() - 1)
        str.append(',');
    }
    return str.toString();
  }

  public static EnvelopeNDLite decodeString(String str, EnvelopeNDLite envelope) {
    String[] parts = str.split(",");
    if (parts.length % 2 == 1)
      throw new RuntimeException(String.format("Number of coordinates is odd in '%s'", str));
    envelope.setCoordinateDimension(parts.length / 2);
    for (int $d = 0; $d < envelope.getCoordinateDimension(); $d++) {
      envelope.setMinCoord($d, Double.parseDouble(parts[$d]));
      envelope.setMaxCoord($d, Double.parseDouble(parts[$d + envelope.getCoordinateDimension()]));
    }
    return envelope;
  }

  public double getArea() {
    double area = 1.0;
    for (int $d = 0; $d < getCoordinateDimension(); $d++)
      area *= this.getSideLength($d);
    return area;
  }

  public Envelope toJTSEnvelope() {
    if (this.getCoordinateDimension() != 2)
      throw new RuntimeException("Can only convert to JTS envelope if number of dimensions is equal to 2");
    return new Envelope(this.getMinCoord(0), this.getMaxCoord(0), this.getMinCoord(1), this.getMaxCoord(1));
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    EnvelopeNDLite that = (EnvelopeNDLite) o;
    return equalsExact(that);
  }

  @Override
  public int hashCode() {
    int result = Arrays.hashCode(minCoord);
    result = 31 * result + Arrays.hashCode(maxCoord);
    return result;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy