Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ jobs:

steps:
- uses: actions/checkout@v3
- name: Set up JDK 11
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '11'
java-version: '17'
distribution: 'temurin'
- name: Build with Gradle
uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0
Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ tasks.named<Test>("test") {

java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(8))
languageVersion.set(JavaLanguageVersion.of(17))
}
withSourcesJar()
withJavadocJar()
Expand Down
200 changes: 200 additions & 0 deletions src/main/java/org/mitre/caasd/commons/AltitudePath.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
package org.mitre.caasd.commons;

import static com.google.common.base.Preconditions.checkArgument;
import static java.util.Objects.nonNull;
import static java.util.Objects.requireNonNull;

import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;

/**
* An AltitudePath is a sequence of altitudes measured in feet. An AltitudePath can be combined with
* a LatLong64Path (or LatLongPath) to produce a sequence of (Lat, Long, Altitude) locations.
* <p>
* AltitudePaths support "null altitudes" by using a constant int value that is close to, but not
* exactly, Integer.MIN_VALUE (i.e., -2147482414)
*
* @param altitudesInFeet
*/
public record AltitudePath(int[] altitudesInFeet) {

/**
* This int represents having "NO ALTITUDE VALUE" at this sequence index. The constant value
* used -2147482414.
*/
public static final int NULL_ALTITUDE = Integer.MIN_VALUE + 1234;

private static final Base64.Encoder BASE_64_ENCODER = Base64.getUrlEncoder().withoutPadding();

public AltitudePath {
requireNonNull(altitudesInFeet);
}

/**
* Convert a List of Distances to an AltitudePath. Any null Distances in the list are converted
* to the NULL_ALTITUDE constant.
*/
public static AltitudePath from(List<Distance> altitudes) {
requireNonNull(altitudes);
int[] alts = altitudes.stream().mapToInt(dist -> asInt(dist)).toArray();
return new AltitudePath(alts);
}

public static AltitudePath from(Distance... dist) {
return from(List.of(dist));
}

/**
* Create an AltitudePath that contains only null altitudes. (This is useful for building
* VehiclePaths from data that does not include altitude data).
*/
public static AltitudePath ofNulls(int n) {
int[] altitudes = new int[n];
Arrays.fill(altitudes, NULL_ALTITUDE);
return new AltitudePath(altitudes);
}

private static int asInt(Distance dist) {
return nonNull(dist) ? (int) dist.inFeet() : NULL_ALTITUDE;
}

/**
* Create a new AltitudePath from an array of bytes that looks like: {altitudeInFeet_0,
* altitudeInFeet_1, altitudeInFeet_2, ...} (each altitude is encoded as one 4-byte int)
*/
public static AltitudePath fromBytes(byte[] bytes) {
requireNonNull(bytes);
checkArgument(bytes.length % 4 == 0, "The byte[] must have a multiple of 4 bytes");

ByteBuffer buffer = ByteBuffer.wrap(bytes);
int[] altData = new int[bytes.length / 4];
for (int i = 0; i < altData.length; i++) {
altData[i] = buffer.getInt();
}

return new AltitudePath(altData);
}

/**
* Create a new AltitudePath object.
*
* @param base64Encoding The Base64 safe and URL safe (no padding) encoding of a AltitudePath's
* byte[]
*
* @return A new AltitudePath object.
*/
public static AltitudePath fromBase64Str(String base64Encoding) {
return AltitudePath.fromBytes(Base64.getUrlDecoder().decode(base64Encoding));
}

public int size() {
return altitudesInFeet.length;
}

public Distance get(int i) {
return (altitudesInFeet[i] == NULL_ALTITUDE) ? null : Distance.ofFeet(altitudesInFeet[i]);
}

/** @return This AltitudePath as a byte[] containing 4 bytes per int in the path */
public byte[] toBytes() {
ByteBuffer buffer = ByteBuffer.allocate(size() * 4);
for (int i = 0; i < altitudesInFeet.length; i++) {
buffer.putInt(altitudesInFeet[i]);
}
return buffer.array();
}

/** @return The Base64 file and url safe encoding of this AltitudePath's byte[] . */
public String toBase64() {
return BASE_64_ENCODER.encodeToString(toBytes());
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(altitudesInFeet);
return result;
}

@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
AltitudePath other = (AltitudePath) obj;
if (!Arrays.equals(altitudesInFeet, other.altitudesInFeet)) return false;
return true;
}

/**
* Compute the "total distance" between the altitudes in these two paths. (Null altitudes are
* treated as if Altitude = 0 for that index)
* <p>
* The distance computed here is the sum of the distances between "altitudes pairs" taken from
* the two paths (e.g. the distance btw the 1st altitude from both paths PLUS the distance btw
* the 2nd altitude from both paths PLUS the distance btw the 3rd altitude from both paths
* ...).
* <p>
* The "distanceBtw" between identical paths will be 0. The "distanceBtw" between nearly
* identical paths will be small. The "distanceBtw" between two very different paths will be
* large.
* <p>
* The computation requires both Paths to have the same size. This is an important requirement
* for making a DistanceMetric using this method.
*
* @param p1 A path
* @param p2 Another path
*
* @return The sum of the pair-wise distance measurements
*/
public static Distance distanceBtw(AltitudePath p1, AltitudePath p2) {
requireNonNull(p1);
requireNonNull(p2);
checkArgument(p1.size() == p2.size(), "Paths must have same size");

return distanceBtw(p1, p2, p1.size());
}

/**
* Compute the "total distance" between the first n altitudes of these two paths. (Null
* altitudes are treated as if Altitude = 0 for that index)
* <p>
* The distance computed here is the sum of the distances between "altitude pairs" taken from
* the two paths (e.g. the distance btw the 1st altitude from both paths PLUS the distance btw
* the 2nd altitude from both paths PLUS the distance btw the 3rd altitude from both paths
* ...).
* <p>
* The "distanceBtw" between two identical paths will be 0. The "distanceBtw" between two nearly
* identical paths will be small. The "distanceBtw" between two very different paths will be
* large.
* <p>
* This The computation requires both Paths to have the same size. This is an important
* requirement for making a DistanceMetric using this method.
*
* @param p1 A path
* @param p2 Another path
* @param n The number of points considered in the "path distance" computation
*
* @return The sum of the pair-wise distance measurements
*/
public static Distance distanceBtw(AltitudePath p1, AltitudePath p2, int n) {
requireNonNull(p1);
requireNonNull(p2);
checkArgument(n >= 0);
checkArgument(p1.size() >= n, "Path1 does not have the required length");
checkArgument(p2.size() >= n, "Path2 does not have the required length");

double distanceSum = 0;
for (int i = 0; i < n; i += 1) {
// if either altitude is missing use 0 as the "stand in" altitude value
int mine = p1.altitudesInFeet[i] == NULL_ALTITUDE ? 0 : p1.altitudesInFeet[i];
int his = p2.altitudesInFeet[i] == NULL_ALTITUDE ? 0 : p2.altitudesInFeet[i];
distanceSum += Math.abs(mine - his);
}

return Distance.ofFeet(distanceSum);
}
}
4 changes: 2 additions & 2 deletions src/main/java/org/mitre/caasd/commons/LatLong64.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* used here saves 50% of the space while maintaining numeric equivalence for the first 7 decimal
* places. This class uses two 32-bit ints to encode latitude and longitude values (as opposed to
* directly storing the values in two 64-bit doubles). This space savings is relevant when you store
* many latitude & longitude pairs as seen in {@link LatLong64Path}.
* many latitude and longitude pairs as seen in {@link LatLong64Path}.
* <p>
* The goal of this class is to support a convenient transition to a more compact form for
* {@link LatLongPath} data. It is NOT the goal of this class to provide a near-duplicate of
Expand All @@ -24,7 +24,7 @@
* LatLong64. "LatLong.of(20*PI, -10*PI)" stores the 2 double primitives: (62.83185307179586,
* -31.41592653589793). Whereas "LatLong64.of(20*PI, -10*PI)" stores 2 ints that equate to the
* values: (62.8318531, -31.4159265). Notice, these approximate values are perfect to the 7th
* decimal place. Geo-Location data is difficult & expensive to measure beyond this level of
* decimal place. Geo-Location data is difficult and expensive to measure beyond this level of
* accuracy.
* <p>
* LatLong64 purposefully does not implement java.io.Serializable. Instead, this class provides 3
Expand Down
11 changes: 5 additions & 6 deletions src/main/java/org/mitre/caasd/commons/LatLong64Path.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
import java.util.stream.Stream;

/**
* This class provides a byte-efficient way to store many latitude & longitude pairs. This class is
* NOT (currently) intended to duplicate the convenience of {@link LatLong} or {@link LatLongPath}.
* It simply provides a convenient transition to a more compact form:
* This class provides a byte-efficient way to store many latitude and longitude pairs. This class
* is NOT (currently) intended to duplicate the convenience of {@link LatLong} or
* {@link LatLongPath}. It simply provides a convenient transition to a more compact form:
* <p>
* The core usage idiom of this class is:
*
Expand Down Expand Up @@ -264,9 +264,8 @@ public static double distanceBtw(LatLong64Path p1, LatLong64Path p2) {
* identical paths will be small. The "distanceBtw" between two very different paths will be
* large.
* <p>
* This
* The computation requires both Paths to have the same size. This is an important requirement
* for making a DistanceMetric using this method.
* This The computation requires both Paths to have the same size. This is an important
* requirement for making a DistanceMetric using this method.
*
* @param p1 A path
* @param p2 Another path
Expand Down
57 changes: 57 additions & 0 deletions src/main/java/org/mitre/caasd/commons/PathPair.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package org.mitre.caasd.commons;

import static com.google.common.base.Preconditions.checkArgument;
import static java.util.Objects.requireNonNull;

/**
* A PathPair combines two VehiclePaths that have the same size.
* <p>
* This record is for finding "similar Vehicle events". The idea is: "When important pair-wise
* vehicle interactions occur extract the path those two vehicles traveled around the time of the
* event. Then, keep a record of that "pair-wise interaction". This allows multiple important
* events that have the "similar paths" to be found.
*
* @param path0
* @param path1
*/
public record PathPair(VehiclePath path0, VehiclePath path1) {

public PathPair {
requireNonNull(path0);
requireNonNull(path1);
checkArgument(path0.size() == path1.size());
}

/** @return The number of "locations" in each path (which must be the same). */
public int size() {
return path0.size();
}

/** Compute the distance between these two paths (use the full paths) */
public static double distanceBtw(PathPair a, PathPair b) {
requireNonNull(a);
requireNonNull(b);
checkArgument(a.size() == b.size(), "Paths must have same size");

return distanceBtw(a, b, a.size());
}

/** Compute the distance between these two paths using just the first n points of the paths */
public static double distanceBtw(PathPair a, PathPair b, int n) {
requireNonNull(a);
requireNonNull(b);
checkArgument(n >= 0);
checkArgument(a.size() >= n, "PathPair1 does not have the required length");
checkArgument(b.size() >= n, "PathPair2 does not have the required length");

// We don't know how to pair off the 4 vehicles in this "Path Pair" comparison

// a0-to-b0 + a1-to-b1
double opt1 = VehiclePath.distanceBtw(a.path0, b.path0, n) + VehiclePath.distanceBtw(a.path1, b.path1);

// a0-to-b1 + a1-to-b0
double opt2 = VehiclePath.distanceBtw(a.path0, b.path1, n) + VehiclePath.distanceBtw(a.path1, b.path0);

return Math.min(opt1, opt2);
}
}
2 changes: 1 addition & 1 deletion src/main/java/org/mitre/caasd/commons/TimeWindow.java
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ public Instant end() {
return end;
}

/** @deprecated */
/** Equivalent to this.duration(). */
public Duration length() {
return duration();
}
Expand Down
Loading
Loading