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

com.geotab.model.coordinate.Coordinate Maven / Gradle / Ivy

package com.geotab.model.coordinate;

import static com.geotab.util.Util.listOf;
import static java.lang.Math.PI;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.geotab.model.serialization.serdes.CoordinateDeserializer;
import java.util.List;
import java.util.Objects;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

/**
 * A coordinate on the earth's surface. "x" is longitude and "y" is latitude.
 */
@Getter @Setter
@NoArgsConstructor
@AllArgsConstructor
@JsonDeserialize(using = CoordinateDeserializer.class)
public class Coordinate {

  /**
   * The longitude.
   */
  private double x;

  /**
   * The latitude.
   */
  private double y;

  /**
   * Calculates the distance between this point and the supplied point.
   *
   * @param x1 The fist x coordinate (longitude).
   * @param y1 The fist y coordinate (latitude).
   * @param x2 The second x coordinate (longitude).
   * @param y2 The second y coordinate (latitude).
   * @return The distance.
   */
  public static double distanceBetween(double x1, double y1, double x2, double y2) {
    x1 = x1 * PI / 180;
    y1 = y1 * PI / 180;
    x2 = x2 * PI / 180;
    y2 = y2 * PI / 180;
    double t1 = Math.sin((y2 - y1) / 2);
    double t2 = Math.sin((x2 - x1) / 2);
    double a = t1 * t1 + Math.cos(y1) * Math.cos(y2) * t2 * t2;
    double c = 2 * Math.asin(Math.min(1f, Math.sqrt(a)));
    return 6367000 * c;
  }

  /**
   * The distance from the closest polygon's border to the point.
   *
   * @param x       The origin's x-coordinate.
   * @param y       The origin's y-coordinate.
   * @param polygon The SimpleCoordinate list represents the the polygon.
   * @return The distance from the polygon.
   */
  public static double distanceFromPoly(double x, double y, List polygon) {
    double minDistance = Double.MAX_VALUE;
    int minIndex = -1;
    int count = polygon.size();
    if (count == 0) {
      return Double.MAX_VALUE;
    }
    for (int i = 0; i < count - 1; i++) {
      Coordinate point = polygon.get(i);
      double distance = distanceBetween(point.x, point.y, x, y);
      if (distance < minDistance) {
        minIndex = i;
        minDistance = distance;
      }
    }
    minDistance = getDistanceToSegment(x, y, polygon, minIndex, minIndex - 1);
    double minDistance1 = getDistanceToSegment(x, y, polygon, minIndex, minIndex + 1);
    return Math.min(minDistance, minDistance1);
  }

  /**
   * Calculates a geographical bearing between two points.
   *
   * @param from The origin coordinates.
   * @param to   The destination coordinates.
   * @return The bearing in degrees.
   */
  public static int getBearing(Coordinate from, Coordinate to) {
    double latFrom = from.y * 0.0174532925199433;
    double longFrom = from.x * 0.0174532925199433;
    double latTo = to.y * 0.0174532925199433;
    double longTo = to.x * 0.0174532925199433;
    double y = Math.sin(longTo - longFrom) * Math.cos(latTo);
    double x = Math.cos(latFrom) * Math.sin(latTo) - Math.sin(latFrom) * Math.cos(latTo) * Math
        .cos(longFrom - longTo);
    int num1 = 360 + (int) (Math.atan2(y, x) * 57.2957795130823);
    return num1 % 360;
  }

  /**
   * Recalculate the extent of collection of coordinates.
   *
   * @param features The features.
   * @return List of coordinates.
   */
  @SuppressWarnings("LocalVariableName")
  public static List getCalculatedExtent(List features) {
    if (features == null) {
      return null;
    }
    int count = features.size();
    if (count == 0) {
      return null;
    }
    Coordinate point = features.get(0);
    double x = point.x;
    double y = point.y;
    double xMin = x;
    double yMin = y;
    double xMax = x;
    double yMax = y;
    for (int i = 1; i < count; i++) {
      point = features.get(i);
      x = point.x;
      y = point.y;
      if (x <= xMin) {
        xMin = x;
      } else if (x >= xMax) {
        xMax = x;
      }
      if (y <= yMin) {
        yMin = y;
      } else if (y >= yMax) {
        yMax = y;
      }
    }
    return rectangleFromLtrb(xMin, yMax, xMax, yMin);
  }

  /**
   * Get the centroid of collection of {@link Coordinate}(s).
   *
   * @param coordinates The coordinates.
   * @return The centroid.
   */
  @SuppressWarnings("VariableDeclarationUsageDistance")
  public static Coordinate getCentroid(List coordinates) {
    if (coordinates == null) {
      return null;
    }
    double x = 0f;
    double y = 0f;
    double area2 = 0f;
    Coordinate basePoint = coordinates.get(0);
    double xb = basePoint.x;
    double yb = basePoint.y;
    double minX = xb;
    double minY = yb;
    double maxX = xb;
    double maxY = yb;
    for (int i = 1; i < coordinates.size() - 1; i++) {
      Coordinate point1 = coordinates.get(i);
      Coordinate point2 = coordinates.get(i + 1);
      double x1 = point1.x;
      double y1 = point1.y;
      double x2 = point2.x;
      double y2 = point2.y;
      if (minX > x1) {
        minX = x1;
      }
      if (maxX < x1) {
        maxX = x1;
      }
      if (minX > x2) {
        minX = x2;
      }
      if (maxX < x2) {
        maxX = x2;
      }
      if (minY > y1) {
        minY = y1;
      }
      if (maxY < y1) {
        maxY = y1;
      }
      if (minY > y2) {
        minY = y2;
      }
      if (maxY < y2) {
        maxY = y2;
      }
      double triangleCenterX3 = x1 + x2 + xb;
      double triangleCenterY3 = y1 + y2 + yb;
      double triangleArea2 = (x1 - xb) * (y2 - yb) - (x2 - xb) * (y1 - yb);
      x += triangleArea2 * triangleCenterX3;
      y += triangleArea2 * triangleCenterY3;
      area2 += triangleArea2;
    }
    if (area2 == 0f) {
      return new Coordinate((maxX - minX) / 2, (maxY - minY) / 2);
    }
    return new Coordinate(x / (3 * area2), y / (3 * area2));
  }

  /**
   * Returns whether the polygon intersects with a specific x and y.
   *
   * @param x       The x-coordinate being tested for intersection.
   * @param y       The y-coordinate being tested for intersection.
   * @param polygon The {@link Coordinate} list (the polygon) being tested for intersection.
   * @return true if the coordinate intersects with the polygon.
   */
  public static boolean intersectsWithPoly(double x, double y, List polygon) {
    if (polygon == null) {
      return false;
    }
    int count = polygon.size();
    if (count == 0) {
      return false;
    }
    Coordinate first;
    Coordinate p1 = first = polygon.get(0);
    Coordinate p2 = polygon.get(1);
    int i = 0;
    boolean intersects = false;
    while (true) {
      double p1Y = p1.y;
      double p2Y = p2.y;
      if (p2Y > y != p1Y > y) {
        double p2X = p2.x;
        if (x < (p1.x - p2X) * (y - p2Y) / (p1Y - p2Y) + p2X) {
          intersects = !intersects;
        }
      }
      if (++i < count) {
        p1 = p2;
        p2 = polygon.get(i);
        continue;
      }
      if (!(p2 == first || p2.equals(first))) {
        throw new IllegalArgumentException("Object must be a polygon");
      }
      return intersects;
    }
  }

  /**
   * Get whether this polygon is a properly formed polygon having the end point equal to the first point.
   *
   * @param feature The feature
   * @return Whether is a polygon or not.
   */
  public static boolean isPolygon(List feature) {
    int count = feature.size();
    if (count == 0) return false;
    return Objects.equals(feature.get(count - 1), feature.get(0));
  }

  /**
   * Gets collection of {@link Coordinate}(s) representing rectangular shape.
   *
   * @param left   The left.
   * @param top    The top.
   * @param right  The right.
   * @param bottom The bottom.
   * @return List of {@link Coordinate}(s).
   */
  public static List rectangleFromLtrb(double left, double top, double right,
      double bottom) {
    Coordinate leftTop = new Coordinate(left, top);
    return listOf(
        leftTop,
        new Coordinate(right, top),
        new Coordinate(right, bottom),
        new Coordinate(left, bottom),
        leftTop
    );
  }


  /**
   * Returns the distance between this point and the supplied point in meters.
   *
   * @param coordinate The {@link Coordinate} being checked for distance.
   * @return The distance.
   */
  public double distanceFrom(Coordinate coordinate) {
    return distanceBetween(x, y, coordinate.x, coordinate.y);
  }

  @Override
  public boolean equals(Object obj) {
    if (!(obj instanceof Coordinate)) {
      return false;
    }
    double delta = x - ((Coordinate) obj).getX();
    if (delta < 0f) {
      delta = -delta;
    }
    if (delta > 1E-07) {
      return false;
    }
    delta = y - ((Coordinate) obj).getY();
    if (delta < 0f) {
      delta = -delta;
    }
    return delta <= 1E-07;
  }

  @Override
  public int hashCode() {
    return roundToEpsilon(x).hashCode() ^ roundToEpsilon(y).hashCode();
  }

  @Override
  public String toString() {
    return "X:" + x + "; Y:" + y;
  }

  static Coordinate getClosestPointOnSegment(double x, double y, Coordinate start, Coordinate end) {
    double x1 = start.x;
    double y1 = start.y;
    double x2 = end.x;
    double y2 = end.y;
    double a = x - x1;
    double b = y - y1;
    double c = x2 - x1;
    double d = y2 - y1;
    double dot = a * c + b * d;
    double lenSq = c * c + d * d;
    double param = dot / lenSq;
    if (lenSq == 0f || param < 0f) {
      return new Coordinate(x1, y1);
    }
    if (param > 1f) {
      return new Coordinate(x2, y2);
    }
    return new Coordinate(x1 + param * c, y1 + param * d);
  }

  static double getDistanceToSegment(double x, double y, List polygon, int start,
      int end) {
    Coordinate point = polygon.get(start);
    if (end < 0 || end >= polygon.size()) {
      return Double.MAX_VALUE;
    }
    Coordinate nextPoint = polygon.get(end);
    Coordinate closest = getClosestPointOnSegment(x, y, nextPoint, point);
    return distanceBetween(x, y, closest.x, closest.y);
  }

  static Double roundToEpsilon(double value) {
    return (int) (value / 1E-07 + 0.5) * 1E-07;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy