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

org.mitre.caasd.commons.Distance Maven / Gradle / Ivy

/*
 *    Copyright 2022 The MITRE Corporation
 *
 *    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 org.mitre.caasd.commons;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.Arrays.asList;
import static org.mitre.caasd.commons.Distance.Unit.FEET;
import static org.mitre.caasd.commons.Distance.Unit.KILOMETERS;
import static org.mitre.caasd.commons.Distance.Unit.METERS;
import static org.mitre.caasd.commons.Distance.Unit.MILES;
import static org.mitre.caasd.commons.Distance.Unit.NAUTICAL_MILES;

import java.io.Serializable;
import java.time.Duration;
import java.util.Iterator;
import java.util.Objects;

import com.google.common.collect.EnumMultiset;
import com.google.common.collect.Iterators;

/**
 * This Distance class is intended to make working with Distances less error prone because (1) all
 * Distance objects are immutable and (2) the unit is always required and always accounted for.
 * 

* This class is extremely similar in spirit and design to java.time.Duration and * java.time.Instant. */ public class Distance implements Serializable, Comparable { //There is currently a quirk when dist.equals(ZERO) will fail if the units are different public static final Distance ZERO = new Distance(0.0, NAUTICAL_MILES); public static final Distance ZERO_FEET = new Distance(0.0, FEET); private final double amount; private final Unit unit; private Distance() { /* * This constructor supports Avro's reflection-based object instantiation. This constructor * is private to prevent "standard users" from seeing it. * * Note, tools that use reflection (e.g. Avro) are the only users who will benefit from this * constructor. Those tools use reflection magic to build the object, then they use more * reflection magic to mutate the values inside the "Immutable object". */ this(0.0, NAUTICAL_MILES); } /** * Create an Immutable Distance object that combines the amount and unit into a single quantity * (ie 5 meters, 7 feet, or 1.3 nautical miles). * * @param amount An amount (cannot be NaN) * @param unit The Distance Unit (ie METERS, FEET, or NAUTICAL_MILES) */ public Distance(double amount, Unit unit) { this.amount = amount; checkArgument(!Double.isNaN(amount), "Distance values of \"Not a Number\" are not supported"); this.unit = checkNotNull(unit); } public enum Unit { KILOMETERS(0.001, "km"), METERS(1.0, "m"), FEET(1.0 / 0.3048, "ft"), //this is the exact ratio as per the definition of feet MILES(1.0 / (0.3048 * 5_280.0), "mi"), //5,280 ft per mile NAUTICAL_MILES(1.0 / 1852.0, "NM"); //this is the exact ratio as per the definition NM private final double unitsPerMeter; private final String abbreviation; Unit(double unitsPerMeter, String suffix) { this.unitsPerMeter = unitsPerMeter; this.abbreviation = suffix; } public double unitsPerMeter() { return unitsPerMeter; } public String abbreviation() { return abbreviation; } } public static Distance of(double amount, Unit unit) { return new Distance(amount, unit); } public static Distance ofMeters(double amount) { return of(amount, METERS); } public static Distance ofKiloMeters(double amount) { return of(amount, KILOMETERS); } public static Distance ofFeet(double amount) { return of(amount, FEET); } public static Distance ofMiles(double amount) { return of(amount, MILES); } public static Distance ofNauticalMiles(double amount) { return of(amount, NAUTICAL_MILES); } public static Distance between(LatLong one, LatLong two) { return one.distanceTo(two); } /** * @return The unit this Distance was originally defined with. */ public Unit nativeUnit() { return this.unit; } /** * Convert this Distance into the desired unit. * * @param desiredUnit A unit like Meters, Feet, or Nautical Miles * * @return This distance convert to a different unit. */ public double in(Unit desiredUnit) { return (this.unit == desiredUnit) ? amount : amount * desiredUnit.unitsPerMeter / this.unit.unitsPerMeter; } public double inMeters() { return in(METERS); } public double inKilometers() { return in(KILOMETERS); } public double inNauticalMiles() { return in(NAUTICAL_MILES); } public double inFeet() { return in(FEET); } public double inMiles() { return in(MILES); } public Distance negate() { return Distance.of(-amount, unit); } public Distance abs() { return Distance.of(Math.abs(amount), unit); } public boolean isPositive() { return amount > 0; } public boolean isNegative() { return amount < 0; } public boolean isZero() { return amount == 0; } public Distance times(double scalar) { return Distance.of(amount * scalar, unit); } public Distance plus(Distance otherDist) { return Distance.of(amount + otherDist.in(unit), unit); } public Distance minus(Distance otherDist) { return plus(otherDist.times(-1.0)); } public boolean isLessThan(Distance otherDist) { return this.amount < otherDist.in(unit); } public boolean isLessThanOrEqualTo(Distance otherDist) { return this.amount <= otherDist.in(unit); } public boolean isGreaterThan(Distance otherDist) { return this.amount > otherDist.in(unit); } public boolean isGreaterThanOrEqualTo(Distance otherDist) { return this.amount >= otherDist.in(unit); } /** * @param otherDist A second distance * * @return The ratio between these distances */ public double dividedBy(Distance otherDist) { return this.amount / otherDist.in(unit); } /** @return The Speed required to travel this distance in this amount of time. */ public Speed dividedBy(Duration lengthOfTime) { return new Speed(this, lengthOfTime); } @Override public int compareTo(Distance o) { return Double.compare(this.inMeters(), o.inMeters()); } @Override public int hashCode() { int hash = 5; hash = 59 * hash + (int) (Double.doubleToLongBits(this.amount) ^ (Double.doubleToLongBits(this.amount) >>> 32)); hash = 59 * hash + Objects.hashCode(this.unit); return hash; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final Distance other = (Distance) obj; if (Double.doubleToLongBits(this.amount) != Double.doubleToLongBits(other.amount)) { return false; } if (this.unit != other.unit) { return false; } return true; } @Override public String toString() { //use more digits after the decimal place when the units are significantly larger if (unit == NAUTICAL_MILES || unit == KILOMETERS || unit == MILES) { return toString(5); } else { return toString(2); } } /** * @param digitsAfterDecimalPlace The number of digits after the decimal place to use. * * @return A String like "1.00m", or "5.143nm" */ public String toString(int digitsAfterDecimalPlace) { return String.format("%." + digitsAfterDecimalPlace + "f" + unit.abbreviation, amount); } /** * Create a Distance object by parsing a String. This method works with both Distance.toString * methods. * * @param parseMe A String like "5.0 km", "5.0km", or "-522ft". * * @return A correctly parsed Distance object * @throws NullPointerException On null input * @throws IllegalArgumentException When a unit cannot be determined * @throws NumberFormatException When a quantity can't be extracted */ public static Distance fromString(String parseMe) { checkNotNull(parseMe); parseMe = parseMe.trim(); Unit unit = unitFromString(parseMe); if (unit == null) { throw new IllegalArgumentException("Could not parse Distance Unit from: " + parseMe); } //store "23.0" instead of "23.0 ft" String parseMeWithoutUnitsSuffix = parseMe.substring(0, parseMe.length() - unit.abbreviation.length()); double amount = Double.parseDouble(parseMeWithoutUnitsSuffix); return Distance.of(amount, unit); } /** * Matches the end of a String with the abbreviation of one of the Distance.Unit types * * @param parseMe A String like "5.0 km", "km", "22ft", or "ft" * * @return The correct Distance.Unit or null if no match can be found. */ public static Distance.Unit unitFromString(String parseMe) { for (Unit unit : Distance.Unit.values()) { if (parseMe.endsWith(unit.abbreviation)) { return unit; } } return null; } public static Distance mean(Distance... distances) { return mean(asList(distances)); } public static Distance mean(Iterable distances) { checkNotNull(distances); checkArgument(distances.iterator().hasNext(), "Collection of Distances cannot be empty"); EnumMultiset unitCounts = EnumMultiset.create(Distance.Unit.class); double sum = 0; for (Distance distance : distances) { sum += distance.inMeters(); unitCounts.add(distance.unit); } double averageInMeters = sum / (double) unitCounts.size(); Distance avg = Distance.ofMeters(averageInMeters); Distance.Unit unit = mostCommonUnit(unitCounts); return Distance.of(avg.in(unit), unit); } public static Distance sum(Distance... distances) { return sum(asList(distances)); } public static Distance sum(Iterable distances) { checkNotNull(distances); if (!distances.iterator().hasNext()) { return Distance.ofMeters(0); } EnumMultiset unitCounts = EnumMultiset.create(Distance.Unit.class); double sumInMeters = 0; for (Distance distance : distances) { sumInMeters += distance.inMeters(); unitCounts.add(distance.unit); } Distance avg = Distance.ofMeters(sumInMeters); Distance.Unit unit = mostCommonUnit(unitCounts); return Distance.of(avg.in(unit), unit); } public static Distance min(Distance... distances) { checkNotNull(distances); return min(Iterators.forArray(distances)); } public static Distance min(Iterable distances) { checkNotNull(distances); return min(distances.iterator()); } public static Distance min(Iterator distances) { checkNotNull(distances); if (!distances.hasNext()) { return null; } Distance currentMin = null; while (distances.hasNext()) { if (currentMin == null) { currentMin = distances.next(); continue; } currentMin = min(currentMin, distances.next()); } return currentMin; } public static Distance min(Distance one, Distance two) { checkNotNull(one); checkNotNull(two); return (one.isLessThanOrEqualTo(two)) ? one : two; } public static Distance max(Distance... distances) { checkNotNull(distances); return max(Iterators.forArray(distances)); } public static Distance max(Iterable distances) { checkNotNull(distances); return max(distances.iterator()); } public static Distance max(Iterator distances) { checkNotNull(distances); if (!distances.hasNext()) { return null; } Distance currentMax = null; while (distances.hasNext()) { if (currentMax == null) { currentMax = distances.next(); continue; } currentMax = max(currentMax, distances.next()); } return currentMax; } public static Distance max(Distance one, Distance two) { checkNotNull(one); checkNotNull(two); return (one.isGreaterThanOrEqualTo(two)) ? one : two; } private static Distance.Unit mostCommonUnit(EnumMultiset unitCounts) { return unitCounts.entrySet().stream() .sorted((e1, e2) -> Integer.compare(e2.getCount(), e1.getCount())) .findFirst().get().getElement(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy