Package de.fhpotsdam.unfolding.utils

Source Code of de.fhpotsdam.unfolding.utils.GeoUtils

package de.fhpotsdam.unfolding.utils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import processing.core.PVector;
import de.fhpotsdam.unfolding.data.Feature;
import de.fhpotsdam.unfolding.data.MultiFeature;
import de.fhpotsdam.unfolding.data.PointFeature;
import de.fhpotsdam.unfolding.data.ShapeFeature;
import de.fhpotsdam.unfolding.geo.Location;
import de.fhpotsdam.unfolding.marker.AbstractShapeMarker;
import de.fhpotsdam.unfolding.marker.Marker;
import de.fhpotsdam.unfolding.marker.MultiMarker;

/**
* Basic geo-spatial utility methods.
*/
public class GeoUtils {

  public static final double EARTH_RADIUS_KM = 6371.01;
  public static final double MIN_LAT = -85.0511d; // top edge in Mercator
  public static final double MAX_LAT = 85.0511d; // bottom edge in Mercator
  public static final double MIN_LON = -180;
  public static final double MAX_LON = 180;

  /**
   * Get distance in kilometers between two points on the earth. Using the great-circle distance formula with the
   * approximated radius of a spherical earth.
   *
   * @param lat1
   *            Latitude of first point, in decimal degrees.
   * @param lon1
   *            Longitude of first point, in decimal degrees.
   * @param lat2
   *            Latitude of second point, in decimal degrees.
   * @param lon2
   *            Longitude of second point, in decimal degrees.
   * @return Distance in kilometers.
   */
  public static double getDistance(double lat1, double lon1, double lat2, double lon2) {
    double lat1Rad = Math.toRadians(lat1);
    double lon1Rad = Math.toRadians(lon1);
    double lat2Rad = Math.toRadians(lat2);
    double lon2Rad = Math.toRadians(lon2);

    double r = EARTH_RADIUS_KM;
    return r
        * Math.acos(Math.sin(lat1Rad) * Math.sin(lat2Rad) + Math.cos(lat1Rad) * Math.cos(lat2Rad)
            * Math.cos(lon2Rad - lon1Rad));
  }

  /**
   * Get distance in kilometers between two points on the earth. Using the great-circle distance formula with the
   * approximated radius of a spherical earth.
   *
   * @param location1
   *            Location of first point
   * @param location2
   *            Location of second point
   * @return Distance in kilometers.
   */
  public static double getDistance(Location location1, Location location2) {
    return getDistance(location1.getLat(), location1.getLon(), location2.getLat(), location2.getLon());
  }

  /**
   * Gets the location specified by a start location, a bearing, and a distance.
   *
   * @param location
   *            The start location.
   * @param bearing
   *            The bearing in degrees.
   * @param distance
   *            The distance in kilometers.
   * @return The destination location.
   */
  public static Location getDestinationLocation(Location location, float bearing, float distance) {
    double lat1 = Math.toRadians(location.getLat());
    double lon1 = Math.toRadians(location.getLon());
    double r = EARTH_RADIUS_KM;
    double b = Math.toRadians(bearing);

    double lat2 = Math.asin(Math.sin(lat1) * Math.cos(distance / r) + Math.cos(lat1) * Math.sin(distance / r)
        * Math.cos(b));
    double lon2 = lon1
        + Math.atan2(Math.sin(b) * Math.sin(distance / r) * Math.cos(lat1),
            Math.cos(distance / r) - Math.sin(lat1) * Math.sin(lat2));
    lon2 = (lon2 + 3 * Math.PI) % (2 * Math.PI) - Math.PI;

    float lat2d = (float) Math.toDegrees(lat2);
    float lon2d = (float) Math.toDegrees(lon2);

    return new Location(lat2d, lon2d);
  }

  /**
   * Gets the angle between two locations.
   *
   * @param location1
   *            First location.
   * @param location2
   *            Second location.
   * @return The angle in radians.
   */
  public static double getAngleBetween(Location location1, Location location2) {
    double rlat1 = Math.toRadians(location1.getLat());
    double rlat2 = Math.toRadians(location2.getLat());
    double rlon1 = Math.toRadians(location1.getLon());
    double rlon2 = Math.toRadians(location2.getLon());

    double angle = (Math.atan2(Math.sin(rlon2 - rlon1) * Math.cos(rlat2),
        Math.cos(rlat1) * Math.sin(rlat2) - Math.sin(rlat1) * Math.cos(rlat2) * Math.cos(rlon2 - rlon1)) % (2 * Math.PI));

    return angle;
  }

  /**
   * Super simplistic method to convert a geo-position as a Location.
   */
  public static Location getDecimal(float latDegrees, float latMinutes, float latSeconds, String latDirection,
      Integer lonDegrees, Integer lonMinutes, Integer lonSeconds, String lonDirection) {
    float lat = getLatitudeDecimal(latDegrees, latMinutes, latSeconds, latDirection);
    float lon = getLongitudeDecimal(lonDegrees, lonMinutes, lonSeconds, lonDirection);
    return new Location(lat, lon);
  }

  public static float getLatitudeDecimal(float latDegrees, float latMinutes, float latSeconds,
      String latDirection) {
    float lat = latDegrees + (latMinutes * 60 + latSeconds) / 3600;
    if (latDirection.equals("S")) {
      lat = -lat;
    }
    return lat;
  }

  public static float getLongitudeDecimal(float lonDegrees, float lonMinutes, float lonSeconds,
      String lonDirection) {
    float lon = lonDegrees + (lonMinutes * 60 + lonSeconds) / 3600;
    if (lonDirection.equals("W")) {
      lon = -lon;
    }
    return lon;
  }

  /**
   * Returns the center of the locations.
   *
   * The returned location minimizes the sum of squared Euclidean distances between itself and each location in the
   * list.
   *
   * @return The centroid location.
   */
  public static Location getEuclideanCentroid(List<Location> locations) {
    Location center = new Location(0, 0);
    for (Location loc : locations) {
      center.add(loc);
    }
    center.div((float) locations.size());
    return center;
  }

  /**
   * Returns the geometric center of the locations of a polygon.
   *
   * The returned location is the center of the polygon, unfazed by unbalanced vertices. (You probably want to use
   * this algorithm, but check out {@link #getEuclideanCentroid(List)} for an alternative.)
   *
   * @return The centroid location.
   */
  public static Location getCentroid(List<Location> originalVertices) {
    List<Location> vertices = getClosedPolygon(originalVertices);
    float cx = 0f, cy = 0f;
    for (int i = 0; i < vertices.size() - 1; i++) {
      PVector vi0 = vertices.get(i);
      PVector vi1 = vertices.get(i + 1);
      cx = cx + (vi0.x + vi1.x) * (vi0.x * vi1.y - vi0.y * vi1.x);
      cy = cy + (vi0.y + vi1.y) * (vi0.x * vi1.y - vi0.y * vi1.x);
    }
    float area = getArea(vertices);
    cx /= (6f * area);
    cy /= (6f * area);
    return new Location(cx, cy);
  }

  /**
   * Returns a closed polygon with the last vertex the same as the first.
   *
   * @param originalVertices
   *            List of locations of original polygon.
   * @return List of location of closed polygon.
   */
  public static List<Location> getClosedPolygon(List<Location> originalVertices) {
    if (originalVertices.size() < 1
        || (originalVertices.get(0).equals(originalVertices.get(originalVertices.size() - 1)))) {
      // Return unchanged, if only one point, or already closed
      return originalVertices;
    }

    List<Location> vertices = new ArrayList<Location>(originalVertices.size() + 1);
    for (int i = 0; i < originalVertices.size(); i++) {
      vertices.add(new Location(0f, 0f));
    }
    Collections.copy(vertices, originalVertices);
    if (vertices.size() > 1) {
      if (!vertices.get(0).equals(vertices.get(vertices.size() - 1))) {
        // Add first vertex on last position to close polygon
        vertices.add(vertices.get(0));
      }
    }
    return vertices;
  }

  /**
   * Calculates the area of a polygon.
   *
   * @param vertices
   *            The vertices of the polygon.
   * @return The area.
   */
  public static float getArea(List<Location> vertices) {
    float sum = 0;
    for (int i = 0; i < vertices.size() - 1; i++) {
      PVector vi0 = vertices.get(i);
      PVector vi1 = vertices.get(i + 1);
      sum += (vi0.x * vi1.y - vi1.x * vi0.y);
    }
    return sum * 0.5f;
  }

  /**
   * Calculates the area of a shape feature.
   *
   * @param feature
   *            The feature containing location vertices.
   * @return The area.
   */
  public static float getArea(Feature feature) {
    return getArea(GeoUtils.getLocations(feature));
  }

  /**
   * Calculates the area of a shape marker.
   *
   * @param marker
   *            The marker containing location vertices.
   * @return The area.
   */
  public static float getArea(Marker marker) {
    return getArea(GeoUtils.getLocations(marker));
  }

  /**
   * Returns the centroid of a feature. Returns the single location for a PointFeature, the geometric center for
   * LineFeature and PolygonFeature, and &mdash; depending on the second parameter &mdash; either the overall
   * geometric center of all features, or the geometric center of the largest feature in a MultiFeature.
   *
   * @param feature
   *            The feature to get the centroid for.
   * @param useLargestForMulti
   *            Set to true if you want to use only the largest feature for {@link MultiFeature}s.
   * @return The location of the geometric center.
   */
  public static Location getCentroid(Feature feature, boolean useLargestForMulti) {
    Location location = null;

    switch (feature.getType()) {
    case POINT:
      location = ((PointFeature) feature).getLocation();
      break;
    case LINES:
    case POLYGON:
      location = GeoUtils.getCentroid(((ShapeFeature) feature).getLocations());
      break;
    case MULTI:
      MultiFeature multiFeature = ((MultiFeature) feature);
      if (useLargestForMulti) {
        // Return centroid of largest feature
        Feature largestFeature = getLargestFeature(multiFeature);
        location = getCentroid(largestFeature);

      } else {

        // Return centroid of all features
        List<Location> locations = new ArrayList<Location>();
        for (Feature f : multiFeature.getFeatures()) {
          Location l = getCentroid(f);
          locations.add(l);
        }
        location = GeoUtils.getCentroid(locations);
      }
      break;
    }

    return location;
  }

  /**
   * Returns the largest feature of a MultiFeature by area size.
   *
   * @param multiFeature
   *            The MultiFeature consisting of multiple features.
   * @return The largest feature.
   */
  public static Feature getLargestFeature(MultiFeature multiFeature) {
    float largestArea = 0;
    Feature largestFeature = null;
    for (Feature f : multiFeature.getFeatures()) {
      if (largestArea < getArea(f)) {
        largestFeature = f;
        largestArea = getArea(f);
      }
    }
    return largestFeature;
  }

  /**
   * Returns the largest marker of a MultiMarker by area size.
   *
   * @param multiMarker
   *            The MultiMarker consisting of multiple markers.
   * @return The largest marker.
   */
  public static Marker getLargestMarker(MultiMarker multiMarker) {
    float largestArea = 0;
    Marker largestMarker = null;
    for (Marker f : multiMarker.getMarkers()) {
      if (largestArea < getArea(f)) {
        largestMarker = f;
        largestArea = getArea(f);
      }
    }
    return largestMarker;
  }

  /**
   * Convenience method for {@link #getCentroid(Feature, boolean)}.
   */
  public static Location getCentroid(Feature feature) {
    return getCentroid(feature, false);
  }

  /**
   * Gets the geometric center of the marker.
   *
   * @param marker
   *            The marker to check.
   * @return The centroid location.
   */
  public static Location getCentroid(Marker marker) {
    return getCentroid(GeoUtils.getLocations(marker));
  }

  /**
   * Gets the overall geometric center of all features.
   *
   * @param features
   *            A list of features.
   * @return The centroid location.
   */
  public static Location getCentroidFromFeatures(List<Feature> features) {
    return GeoUtils.getCentroid(GeoUtils.getLocationsFromFeatures(features));
  }

  /**
   * Returns all locations of all features.
   *
   * @param features
   *            A list of features.
   * @return A list of locations.
   */
  public static List<Location> getLocationsFromFeatures(List<Feature> features) {
    List<Location> locations = new ArrayList<Location>();
    for (Feature feature : features) {
      locations.addAll(getLocations(feature));
    }
    return locations;
  }

  /**
   * Returns all locations of a feature. That is a single location for a point, all locations for lines or polygons,
   * and all locations of all features of a MultiFeature.
   *
   * @param feature
   *            The feature to get locations from.
   * @return A list of locations.
   */
  public static List<Location> getLocations(Feature feature) {
    List<Location> locations = new ArrayList<Location>();
    if (feature.getType() == Feature.FeatureType.POINT) {
      PointFeature pf = (PointFeature) feature;
      locations.add(pf.getLocation());
    }
    if (feature.getType() == Feature.FeatureType.LINES || feature.getType() == Feature.FeatureType.POLYGON) {
      ShapeFeature sf = (ShapeFeature) feature;
      locations.addAll(sf.getLocations());
    }
    if (feature.getType() == Feature.FeatureType.MULTI) {
      MultiFeature multiFeature = (MultiFeature) feature;
      for (Feature f : multiFeature.getFeatures()) {
        locations.addAll(getLocations(f));
      }
    }
    return locations;
  }

  /**
   * Returns all locations of all markers.
   *
   * @param markers
   *            A list of markers.
   * @return A list of locations.
   */
  public static List<Location> getLocationsFromMarkers(List<Marker> markers) {
    List<Location> locations = new ArrayList<Location>();
    for (Marker marker : markers) {
      locations.addAll(getLocations(marker));
    }
    return locations;
  }

  /**
   * Returns all locations of a marker. That is a single location for a point, all locations for lines or polygons,
   * and all locations of all markers of a MultiMarker.
   *
   * @param marker
   *            The marker to get locations from.
   * @return A list of locations.
   */
  public static List<Location> getLocations(Marker marker) {
    List<Location> locations = new ArrayList<Location>();
    if (marker instanceof MultiMarker) {
      // recursive for multi
      MultiMarker mm = (MultiMarker) marker;
      for (Marker m : mm.getMarkers()) {
        locations.addAll(getLocations(m));
      }
    } else if (marker instanceof AbstractShapeMarker) {
      // line or polygon
      AbstractShapeMarker sm = (AbstractShapeMarker) marker;
      locations.addAll(sm.getLocations());
    } else {
      // default: point
      locations.add(marker.getLocation());
    }
    return locations;
  }

  /**
   * Returns the bounding box for the list of locations.
   *
   * Does not return the minimum bounding box, i.e. locations spanning the 180th meridian will result in a very large
   * box.
   *
   * @param locations
   *            List of locations to get the bounding box for.
   * @return An array of two locations consisting of the north-west and the south-east bounds.
   */
  public static Location[] getBoundingBox(List<Location> locations) {
    Location nwLocation = new Location(GeoUtils.MIN_LAT, GeoUtils.MAX_LON);
    Location seLocation = new Location(GeoUtils.MAX_LAT, GeoUtils.MIN_LON);

    for (Location location : locations) {
      if (location.getLat() > nwLocation.getLat()) {
        nwLocation.setLat(location.getLat());
      }
      if (location.getLon() < nwLocation.getLon()) {
        nwLocation.setLon(location.getLon());
      }

      if (location.getLat() < seLocation.getLat()) {
        seLocation.setLat(location.getLat());
      }
      if (location.getLon() > seLocation.getLon()) {
        seLocation.setLon(location.getLon());
      }
    }

    return new Location[] { nwLocation, seLocation };
  }

  public static List<Location> decodePolyline(String encoded) {
    return decodePolyline(encoded, 5);
  }

  public static List<Location> decodeOSRMPolyline(String encoded) {
    return decodePolyline(encoded, 6);
  }

  /**
   * Decodes an encoded polyline string to a list of locations. Polyline format is used by various geo services.
   *
   * @see <a
   *      href="https://github.com/DennisSchiefer/Project-OSRM-Web/blob/develop/WebContent/routing/OSRM.RoutingGeometry.js">RoutingGeometry.js
   *      - Adapted algorithm by OSRM (precision: 6 digits)</a>
   * @see <a href="https://developers.google.com/maps/documentation/utilities/polylinealgorithm">Encoded Polyline
   *      Algorithm Format - Original algorithm by Google (precision: 5 digits)</a>
   *
   * @param encoded
   *            The encoded String.
   * @return An list of locations.
   */
  public static List<Location> decodePolyline(String encoded, int precision) {
    List<Location> poly = new ArrayList<Location>();

    double precisionMult = Math.pow(10, -precision);

    int index = 0, len = encoded.length();
    int lat = 0, lng = 0;
    while (index < len) {
      int b, shift = 0, result = 0;
      do {
        b = encoded.charAt(index++) - 63;
        result |= (b & 0x1f) << shift;
        shift += 5;
      } while (b >= 0x20);
      int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
      lat += dlat;
      shift = 0;
      result = 0;
      do {
        b = encoded.charAt(index++) - 63;
        result |= (b & 0x1f) << shift;
        shift += 5;
      } while (b >= 0x20);
      int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
      lng += dlng;

      Location p = new Location((double) lat * precisionMult, (double) lng * precisionMult);
      poly.add(p);
    }
    return poly;
  }

}
TOP

Related Classes of de.fhpotsdam.unfolding.utils.GeoUtils

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.