it.unibo.alchemist.model.implementations.positions.LatLongPosition.java Source code

Java tutorial

Introduction

Here is the source code for it.unibo.alchemist.model.implementations.positions.LatLongPosition.java

Source

/*
 * Copyright (C) 2010-2014, Danilo Pianini and contributors
 * listed in the project's pom.xml file.
 * 
 * This file is part of Alchemist, and is distributed under the terms of
 * the GNU General Public License, with a linking exception, as described
 * in the file LICENSE in the Alchemist distribution's top directory.
 */
package it.unibo.alchemist.model.implementations.positions; // NOPMD by danysk on 2/4/14 3:39 PM

import static org.apache.commons.math3.util.FastMath.abs;
import static org.apache.commons.math3.util.FastMath.acos;
import static org.apache.commons.math3.util.FastMath.asin;
import static org.apache.commons.math3.util.FastMath.atan2;
import static org.apache.commons.math3.util.FastMath.cos;
import static org.apache.commons.math3.util.FastMath.sin;
import static org.apache.commons.math3.util.FastMath.sqrt;
import static org.apache.commons.math3.util.FastMath.toDegrees;
import static org.apache.commons.math3.util.FastMath.toRadians;

import java.util.List;
import java.util.function.BinaryOperator;

import org.danilopianini.util.Hashes;

import it.unibo.alchemist.exceptions.UncomparableDistancesException;
import it.unibo.alchemist.model.interfaces.GeoPosition;
import it.unibo.alchemist.model.interfaces.Position;

import com.google.common.collect.Lists;
import com.javadocmd.simplelatlng.LatLng;
import com.javadocmd.simplelatlng.util.LengthUnit;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

/**
 * Unmodifiable state version of {@link LatLng}, also implementing the
 * {@link Position} interface.
 * 
 */
public final class LatLongPosition implements GeoPosition {

    /**
     * The default distance formula.
     */
    public static final DistanceFormula DEFAULT_DISTANCE_FORMULA = DistanceFormula.EQUIRECTANGULAR;
    /**
     * Mean Earth radius in meters.
     */
    public static final double EARTH_MEAN_RADIUS_METERS = 6371009;
    private static final double MAX_LAT = Math.toRadians(90d); // PI/2
    private static final double MAX_LON = Math.toRadians(180d); // PI
    private static final double MIN_LAT = Math.toRadians(-90d); // -PI/2
    private static final double MIN_LON = Math.toRadians(-180d); // -PI

    private static final long serialVersionUID = -8972065367390749356L;

    private final DistanceFormula df;
    private final LatLng latlng;
    private int hash;

    /**
     * Possible methods to compute the distance between two latitude-longitude
     * points.
     * 
     */
    public enum DistanceFormula {

        /**
         * 
         */
        EQUIRECTANGULAR, HAVERSINE, SPHERICAL_COSINES
    }

    /**
     * Distance between two points.
     * 
     * @param point1
     *            the first point.
     * @param point2
     *            the second point.
     * @param df
     *            the formula to use to compute distances
     * @return the distance in the chosen unit of measure.
     */
    public static double distance(final LatLng point1, final LatLng point2, final DistanceFormula df) {
        return distanceInRadians(point1, point2, df) * EARTH_MEAN_RADIUS_METERS;
    }

    /**
     * Distance between two points with arbitrary {@link LengthUnit}.
     * 
     * @param point1
     *            the first point.
     * @param point2
     *            the second point.
     * @param unit
     *            the unit of measure in which to receive the result.
     * @param df
     *            the formula to use to compute distances
     * @return the distance in the chosen unit of measure.
     */
    public static double distance(final LatLng point1, final LatLng point2, final LengthUnit unit,
            final DistanceFormula df) {
        return LengthUnit.METER.convertTo(unit, distance(point1, point2, df));
    }

    /**
     * <p>
     * This "distance" function is mostly for internal use. Most users will
     * simply rely upon {@link #distance(LatLng, LatLng, LengthUnit, DistanceFormula)}
     * </p>
     * 
     * <p>
     * Yields the internal angle for an arc between two points on the surface of
     * a sphere in radians. This angle is in the plane of the great circle
     * connecting the two points measured from an axis through one of the points
     * and the center of the Earth. Multiply this value by the sphere's radius
     * to get the length of the arc.
     * </p>
     * 
     * @param point1
     *            the first point
     * @param point2
     *            the second point
     * @param precision
     *            the formula to use
     * @return the internal angle for the arc connecting the two points in
     *         radians.
     */
    public static double distanceInRadians(final LatLng point1, final LatLng point2,
            final DistanceFormula precision) {
        final double lat1R = toRadians(point1.getLatitude());
        final double lat2R = toRadians(point2.getLatitude());
        final double lon1R = toRadians(point1.getLongitude());
        final double lon2R = toRadians(point2.getLongitude());
        switch (precision) {
        case HAVERSINE:
            final double dLatR = abs(lat2R - lat1R);
            final double dLngR = abs(lon2R - lon1R);
            final double a = sin(dLatR / 2) * sin(dLatR / 2)
                    + cos(lat1R) * cos(lat2R) * sin(dLngR / 2) * sin(dLngR / 2);
            return 2 * atan2(sqrt(a), sqrt(1 - a));
        case SPHERICAL_COSINES:
            return acos(sin(lat1R) * sin(lat2R) + cos(lat1R) * cos(lat2R) * cos(abs(lon2R - lon1R)));
        case EQUIRECTANGULAR:
            final double x = (lon2R - lon1R) * cos((lat1R + lat2R) / 2);
            final double y = lat2R - lat1R;
            return sqrt(x * x + y * y);
        default:
            throw new IllegalStateException("Unknown algorithm required: " + precision);
        }
    }

    /**
     * @param lat
     *            latitude
     * @param lon
     *            longitude
     */
    public LatLongPosition(final double lat, final double lon) {
        this(lat, lon, DEFAULT_DISTANCE_FORMULA);
    }

    /**
     * @param lat
     *            latitude
     * @param lon
     *            longitude
     * @param distanceFormula
     *            the formula to use to compute distances
     */
    public LatLongPosition(final double lat, final double lon, final DistanceFormula distanceFormula) {
        latlng = new LatLng(lat, lon);
        df = distanceFormula;
    }

    /**
     * @param lat
     *            latitude
     * @param lon
     *            longitude
     * @param distanceFormula
     *            the index of the formula to use to compute distances
     */
    public LatLongPosition(final double lat, final double lon, final int distanceFormula) {
        this(lat, lon, DistanceFormula.values()[distanceFormula % DistanceFormula.values().length]);
    }

    /**
     * @param lat
     *            latitude
     * @param lon
     *            longitude
     */
    public LatLongPosition(final Number lat, final Number lon) {
        this(lat.doubleValue(), lon.doubleValue());
    }

    @Override
    public List<Position> buildBoundingBox(final double range) {
        if (range < 0d) {
            throw new IllegalArgumentException("Negative ranges make no sense.");
        }
        /*
         *  angular distance in radians on a great circle
         */
        final double radDist = range / EARTH_MEAN_RADIUS_METERS;
        final double radLat = toRadians(getLatitude());
        final double radLon = toRadians(getLongitude());

        double minLat = radLat - radDist;
        double maxLat = radLat + radDist;

        double minLon;
        double maxLon;
        if (minLat > MIN_LAT && maxLat < MAX_LAT) {
            final double deltaLon = asin(Math.sin(radDist) / cos(radLat));
            minLon = radLon - deltaLon;
            if (minLon < MIN_LON) {
                minLon += 2d * Math.PI;
            }
            maxLon = radLon + deltaLon;
            if (maxLon > MAX_LON) {
                maxLon -= 2d * Math.PI;
            }
        } else {
            // a pole is within the distance
            minLat = Math.max(minLat, MIN_LAT);
            maxLat = Math.min(maxLat, MAX_LAT);
            minLon = MIN_LON;
            maxLon = MAX_LON;
        }
        return Lists.newArrayList(new LatLongPosition(toDegrees(minLat), toDegrees(minLon)),
                new LatLongPosition(toDegrees(maxLat), toDegrees(maxLon)));
    }

    @Override
    public double[] getCartesianCoordinates() {
        return new double[] { getLongitude(), getLatitude() };
    }

    @Override
    public double getCoordinate(final int dim) {
        if (dim == 0) {
            return getLongitude();
        }
        if (dim == 1) {
            return getLatitude();
        }
        throw new IllegalArgumentException("Pass 0 for longitude and 1 for latitude. No other value accepted.");
    }

    /**
     * @return the latitude
     */
    public double getLatitude() {
        return latlng.getLatitude();
    }

    /**
     * @return the longitude
     */
    public double getLongitude() {
        return latlng.getLongitude();
    }

    @Override
    public int getDimensions() {
        return 2;
    }

    @Override
    public double getDistanceTo(final Position p) {
        if (p instanceof LatLongPosition) {
            return distance(latlng, ((LatLongPosition) p).latlng, df);
        }
        final int pDims = p.getDimensions();
        if (pDims == 2) {
            final double[] coords = p.getCartesianCoordinates();
            return distance(latlng, new LatLng(coords[1], coords[0]), df);
        }
        throw new UncomparableDistancesException(this, p);
    }

    @Override
    @SuppressFBWarnings(justification = "Exact floating point equality is required here.")
    public boolean equals(final Object obj) {
        if (obj instanceof LatLongPosition) {
            final LatLongPosition llp = (LatLongPosition) obj;
            return getLatitude() == llp.getLatitude() && getLongitude() == llp.getLongitude();
        }
        return false;
    }

    @Override
    public int hashCode() {
        if (hash == 0) {
            hash = Hashes.hash32(getLatitude(), getLongitude());
        }
        return hash;
    }

    @Override
    public String toString() {
        return latlng.toString();
    }

    @Override
    public GeoPosition add(final Position other) {
        return ebeOperation((self, b) -> self + b, other);
    }

    @Override
    public GeoPosition subtract(final Position other) {
        return ebeOperation((self, o) -> self - o, other);
    }

    private LatLongPosition ebeOperation(final BinaryOperator<Double> op, final Position other) {
        if (other instanceof LatLongPosition) {
            final LatLng l2 = ((LatLongPosition) other).latlng;
            return new LatLongPosition(op.apply(latlng.getLatitude(), l2.getLatitude()),
                    op.apply(latlng.getLongitude(), l2.getLongitude()));
        }
        throw new IllegalArgumentException(
                "You are trying to do math combining " + this + "with " + other + ". This is not supported.");
    }

}