bgabriel998/SoftwareDevProject

View on GitHub
app/src/main/java/ch/epfl/sdp/peakar/points/Point.java

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
package ch.epfl.sdp.peakar.points;

import androidx.core.util.Pair;

import org.osmdroid.util.BoundingBox;

/**
 * Point is a class that represents a general point on earth.
 * A point is described by three components:
 * <ul>
 * <li>Latitude
 * <li>Longitude
 * <li>Altitude
 * </ul>
 * <p>
 * This class contains a method to compute distance in meters between two points.
 */
public class Point {

    final static double EARTH_RADIUS = 6378137; // value in meters
    private final double POLAR_RADIUS_EARTH = 6356752.3; //in meters
    private final double ECCENTRICITY_SQUARED = 0.00669437999014; //in meters
    private final double ADJUST_COORDINATES = 0.008983112; // 1km in degrees at equator.

    protected double latitude;
    protected double longitude;

    protected double altitude;

    private double horizontalBearing;
    private double verticalBearing;
    private double distanceToUser;

    /**
     * Constructor for the Point class.
     *
     * @param latitude  latitude of the point (in degrees)
     * @param longitude longitude of the point (in degrees)
     * @param altitude  altitude of the point (in meters)
     */
    public Point(double latitude, double longitude, double altitude) {
        this.latitude = latitude;
        this.longitude = longitude;
        this.altitude = altitude;
    }

    /**
     * Method for computing distance between two point as the crow flies.
     *
     * @param other the other point to compute the distance
     * @return      a value in meters representing the distance
     */
    public double computeDistance(Point other) {

        double rThis = EARTH_RADIUS + this.altitude;
        double rOther = EARTH_RADIUS + other.altitude;

        return computeSphericalDistance(other, rThis, rOther);

    }

    /**
     * Method for computing distance between two point as the crow flies,
     * without taking in account the altitude of the two points
     *
     * @param other the other point to compute the distance
     * @return      a value in meters representing the distance
     */
    public double computeFlatDistance(Point other) {
        return computeSphericalDistance(other, EARTH_RADIUS, EARTH_RADIUS);
    }

    /**
     * Given the radious of the two points, it computes the distance in spherical coordinates
     *
     * @param other     the other point to compute the distance
     * @param rThis     the radius of this point
     * @param rOther    the radius of the other point
     * @return          a value in meters representing the distance
     */
    private double computeSphericalDistance(Point other, double rThis, double rOther) {

        double squaredDistance;

        double latThis = Math.toRadians(this.latitude);
        double lonThis = Math.toRadians(this.longitude);

        double latOther = Math.toRadians(other.latitude);
        double lonOther = Math.toRadians(other.longitude);

        // computing distance in spherical polar coordinates
        squaredDistance = Math.pow(rThis, 2) + Math.pow(rOther, 2) -
                2*rThis*rOther*(
                        Math.cos(latThis)*Math.cos(latOther)*Math.cos(lonThis - lonOther) +
                                Math.sin(latThis)*Math.sin(latOther)
                );

        return Math.sqrt(squaredDistance);

    }

    /**
     *
     * @param latitude  latitude to set (in degrees)
     */
    public void setLatitude(double latitude) { this.latitude = latitude; }

    /**
     *
     * @param longitude longitude to set (in degrees)
     */
    public void setLongitude(double longitude) {
        this.longitude = longitude;
    }

    /**
     *
     * @param altitude altitude to set (in meters)
     */
    public void setAltitude(double altitude) {
        this.altitude = altitude;
    }

    /**
     *
     * @return point latitude (in degrees)
     */
    public double getLatitude() {
        return latitude;
    }

    /**
     *
     * @return point longitude (in degrees)
     */
    public double getLongitude() {
        return longitude;
    }

    /**
     *
     * @return point altitude (in meters)
     */
    public double getAltitude() {
        return altitude;
    }

    /**
     * gets the distance relative to the set userPoint
     * @return distance in meters between point and set userPoint
     */
    public double getDistanceToUser(){
        return distanceToUser;
    }

    /**
     * Sets the distance between the point and the given userPoint
     * @param userPoint userPoint for which the distance will be computed.
     */
    public void setDistanceToUser(UserPoint userPoint){
        distanceToUser = this.computeDistance(userPoint);
    }

    /**
     * Gets the horizontal bearing
     * @return horizontal bearing in degrees
     */
    public double getHorizontalBearing() {
        return horizontalBearing;
    }

    /**
     * Computes the horizontal bearing from a given startpoint
     * @param startPoint point from where the bearing is calculated
     * @return new calculated bearing in degrees
     */
    public double setHorizontalBearing(Point startPoint) {
        this.horizontalBearing = computeHorizontalBearing(startPoint);
        return horizontalBearing;
    }

    /**
     * Gets the vertical bearing
     * @return vertical bearing in degrees
     */
    public double getVerticalBearing() {
        return verticalBearing;
    }

    /**
     * Computes the vertical bearing from a given startpoint
     * @param startPoint point from where the bearing is calculated
     * @return new calculated bearing in degrees
     */
    public double setVerticalBearing(Point startPoint) {
        this.verticalBearing = computeVerticalBearing(startPoint);
        return verticalBearing;
    }

    /**
     * Calculates the horizontal angle between the point and another point
     * Using the great circle path formula
     * @param startPoint point from where we calculate the bearing
     * @return Angle in degrees between 0 and 360
     */
    private double computeHorizontalBearing(Point startPoint){
        double startLat = Math.toRadians(startPoint.getLatitude());
        double startLon = startPoint.getLongitude();
        double endLat = Math.toRadians(this.getLatitude());
        double endLon = this.getLongitude();

        double deltaLong = Math.toRadians(endLon - startLon);
        double y = Math.sin(deltaLong) * Math.cos(Math.toRadians(endLat));
        double x = Math.cos(startLat) * Math.sin(endLat)
                - Math.sin(startLat) * Math.cos(endLat) * Math.cos(deltaLong);
        return (Math.toDegrees(Math.atan2(y, x)) + 360) % 360;
    }

    /**
     * Calculates the vertical angle between a startpoint and the POIPoint
     * See: http://cosinekitty.com/compass.html
     * @param startPoint Point from where the angle is calculated
     * @return Angle in degrees between 0° (mountain exactly below) and
     * 180° (mountain exactly above), horizon is 90°
     */
    private double computeVerticalBearing(Point startPoint){
        Pair<Point, Point> ap = locationToPoint(startPoint);
        Pair<Point, Point> bp = locationToPoint(this);
        Point apGeocentric = ap.first;
        Point apGeodetic = ap.second;
        Point bpGeocentric = bp.first;

        assert bpGeocentric != null;
        assert apGeocentric != null;
        Point bma = normalizeVectorDiff(bpGeocentric, apGeocentric);

        assert apGeodetic != null;
        double delta = bma.getLatitude() * apGeodetic.getLatitude() +
                bma.getLongitude() * apGeodetic.getLongitude() + bma.getAltitude() * apGeodetic.getAltitude();

        double elevation = Math.acos(-1*delta);
        return Math.toDegrees(elevation);
    }

    /**
     * Uses two points to create a new normalized Point
     * @param b first Point
     * @param a second Point
     * @return Point that is normalized
     */
    private Point normalizeVectorDiff(Point b, Point a){
        // Calculate norm(b-a), where norm divides a vector by its length to produce a unit vector.
        double dx = b.getLatitude() - a.getLatitude();
        double dy = b.getLongitude() - a.getLongitude();
        double dz = b.getAltitude() - a.getAltitude();
        double dist2 = dx*dx + dy*dy + dz*dz;
        double dist = Math.sqrt(dist2);
        return new Point(dx/dist, dy/dist, dz/dist);
    }

    /**
     * Converts a Point of geodetic coordinate system to a geocentric coordinate system
     * @param c Point that gets computed
     * @return Pair<Point, Point></> the first point represents the geocentric point and the second
     * Point the normal vector from the surface.
     */
    private Pair<Point, Point> locationToPoint(Point c){
        // Convert (lat, lon, elv) to (x, y, z).
        double lat = c.getLatitude() * Math.PI / 180.0;
        double lon = c.getLongitude() * Math.PI / 180.0;
        double elv = c.getAltitude();

        double radius = earthRadiusInMeters(lat);
        double clat   = geocentricLatitude(lat);

        double cosLon = Math.cos(lon);
        double sinLon = Math.sin(lon);

        double cosLat = Math.cos(clat);
        double sinLat = Math.sin(clat);
        double x = radius * cosLon * cosLat;
        double y = radius * sinLon * cosLat;
        double z = radius * sinLat;

        // We used geocentric latitude to calculate (x,y,z) on the Earth's ellipsoid.
        // Now we use geodetic latitude to calculate normal vector from the surface, to correct for elevation.
        double cosGlat = Math.cos(lat);
        double sinGlat = Math.sin(lat);

        double nx = cosGlat * cosLon;
        double ny = cosGlat * sinLon;
        double nz = sinGlat;

        x += elv * nx;
        y += elv * ny;
        z += elv * nz;

        return new Pair<>(new Point(x, y, z), new Point(nx, ny, nz));
    }

    /**
     * Calculates the earth radius given the latitude
     * See http://en.wikipedia.org/wiki/Earth_radius
     * @param latitudeRadians latitude in radians (geodetic)
     * @return Radius of the earth in meters
     */
    private double earthRadiusInMeters(double latitudeRadians){
        double cos = Math.cos (latitudeRadians);
        double sin = Math.sin (latitudeRadians);
        double t1 = EARTH_RADIUS * EARTH_RADIUS * cos;
        double t2 = POLAR_RADIUS_EARTH * POLAR_RADIUS_EARTH * sin;
        double t3 = EARTH_RADIUS * cos;
        double t4 = POLAR_RADIUS_EARTH * sin;
        return Math.sqrt ((t1*t1 + t2*t2) / (t3*t3 + t4*t4));
    }

    /**
     * Converts a geodetic latitude (like GPS values) to a geocentric latitude (angle measured
     * from center of Earth between a point and the equator).
     * See https://en.wikipedia.org/wiki/Latitude#Geocentric_latitude
     * @param lat Geodetic latitude in radian
     * @return Converted latitude in radian to geocentric latitude
     */
    private double geocentricLatitude(double lat){
        return Math.atan((1.0 - ECCENTRICITY_SQUARED) * Math.tan(lat));
    }

    /**
     * Computes bounding box around
     * @param rangeInKm range around user point to compute the bounding box (in km)
     * @return Bounding box around point
     */
    public BoundingBox computeBoundingBox(double rangeInKm){
        double north = latitude + ( rangeInKm * ADJUST_COORDINATES);
        double south = latitude - ( rangeInKm * ADJUST_COORDINATES);
        double lngRatio = 1/Math.cos(Math.toRadians(latitude));
        double east = longitude + (rangeInKm * ADJUST_COORDINATES) * lngRatio;
        double west = longitude - (rangeInKm * ADJUST_COORDINATES) * lngRatio;
        return new BoundingBox(north,east,south,west);
    }

}