mirror of
https://github.com/JHUAPL/Picante.git
synced 2026-01-08 19:37:54 -05:00
initial commit
This commit is contained in:
15
.gitignore
vendored
Normal file
15
.gitignore
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
/target/
|
||||
.classpath
|
||||
.project
|
||||
.settings/
|
||||
/data/
|
||||
/idea/
|
||||
/.idea/
|
||||
/bin/
|
||||
/target/
|
||||
.DS_Store
|
||||
*.DS_Store
|
||||
|
||||
# binary kernels in src/main/resources
|
||||
*.bc
|
||||
*.bsp
|
||||
8
LICENSE.md
Normal file
8
LICENSE.md
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
Copyright 2023, The Johns Hopkins University Applied Physics Laboratory
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
65
README.md
Normal file
65
README.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# Picante
|
||||
|
||||
Picante is a Java implementation of the core functionality of [NAIF SPICE](https://naif.jpl.nasa.gov).
|
||||
|
||||
Key features:
|
||||
|
||||
- Pure Java; no native libraries needed
|
||||
- replicates NAIF SPICE ephemeris calculations and frame transforms to 1e-12 precision
|
||||
- Thread safe
|
||||
|
||||
Although Picante reproduces many SPICE capabilities, it is implemented very differently. Kernels are loaded into a "
|
||||
SpiceEnvironment". The user can instantiate multiple SpiceEnvironments to compare calculations with different sets of
|
||||
kernels, or to run a multithreaded application.
|
||||
|
||||
Picante was developed by the Analysis and Applications group of the Space Exploration Sector of the Johns Hopkins
|
||||
University Applied Physics Laboratory.
|
||||
It is released under the [MIT](LICENSE.md) License.
|
||||
|
||||
## Maven Coordinates
|
||||
|
||||
```
|
||||
<dependency>
|
||||
<groupId>edu.jhuapl.ses</groupId>
|
||||
<artifactId>picante</artifactId>
|
||||
<version>0.0.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### Demo Code
|
||||
|
||||
The [unit tests](src/test/java/picante/demo) show how to replicate many common NAIF functions using Picante.
|
||||
The [demo](src/main/java/picante/demo) package contains a longer example, modeled on an example in the
|
||||
SPICE [tutorials](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/Tutorials/pdf/individual_docs/38_program_idl.pdf).
|
||||
|
||||
Many of the binary CK and SPK files needed to run the examples are not included in this repository. Run
|
||||
the [getCK](src/test/resources/kernels/ck/getCK.bash) and [getSPK](src/test/resources/kernels/spk/getSPK.bash) scripts
|
||||
to download them from NAIF.
|
||||
|
||||
## Incompatibilities with NAIF/SPICE:
|
||||
|
||||
- CK types other than 2 or 3 are not supported.
|
||||
- Unsupported SPK types:
|
||||
- 10
|
||||
- 14
|
||||
- 15
|
||||
- 18 and higher
|
||||
- DSK files are not supported.
|
||||
- Product and Switch frames (new in N0067) are not supported.
|
||||
- Time format incompatibilities. Picante's [TimeConversion](src/main/java/picante/time/TimeConversion.java) class can parse all the example time format strings listed in
|
||||
the notes for [STR2ET](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/FORTRAN/spicelib/str2et.html).
|
||||
- All times are assumed to be UTC. Neither time system modifiers (e.g. TT, TDT, TDB, etc) nor U.S. Time zones are
|
||||
supported.
|
||||
- TimeConversion fails on "2000 Jan 01 00:00:00 PST" but STR2ET will accept it.
|
||||
- TimeConversion fails on "2000 Jan 01 00:00:00 TDB" but STR2ET will accept it.
|
||||
- STR2ET will fail on the following examples, but TimeConversion will parse them:
|
||||
- 1997 Jan 32 12:29:29 translates to 1997-02-01T12:29:29.000
|
||||
- 1997 Feb 29, 12:29:20.0 translates to 1997-03-01T12:29:20.000
|
||||
- 1992 Mar 12 12:62:20 translates to 1992-03-12T13:02:20.000
|
||||
- 1993 Mar 18 15:29:60.5 translates to 1993-03-18T15:30:00.500
|
||||
- Built-in enumerations containing commonly used solar system bodies (`CelestialBodies`) and frames (`CelestialFrames`)
|
||||
have entries that deviate from the standard NAIF string names:
|
||||
- `DE-###` frames have the minus sign removed: `DE###`
|
||||
- The asteroid `52_EUROPA` is assigned the entry: `A52_EUROPA`
|
||||
- The corresponding IAU body-fixed frame is: `IAU_A52_EUROPA` to preserve the `IAU_` prefix property commonly
|
||||
expected.
|
||||
160
pom.xml
Normal file
160
pom.xml
Normal file
@@ -0,0 +1,160 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>edu.jhuapl.ses</groupId>
|
||||
<artifactId>picante</artifactId>
|
||||
<version>0.0.1</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>picante</name>
|
||||
<description>Picante is a pure Java implementation of the core function of the NAIF/SPICE library (https://naif.jpl.nasa.gov/naif/).</description>
|
||||
<url>https://github.com/JHUAPL/Picante</url>
|
||||
|
||||
<inceptionYear>2023</inceptionYear>
|
||||
<organization>
|
||||
<name>Johns Hopkins University Applied Physics Laboratory</name>
|
||||
<url>https://www.jhuapl.edu/</url>
|
||||
</organization>
|
||||
|
||||
<licenses>
|
||||
<license>
|
||||
<name>MIT License</name>
|
||||
<url>https://opensource.org/license/mit/</url>
|
||||
</license>
|
||||
</licenses>
|
||||
|
||||
<developers>
|
||||
<developer>
|
||||
<id>HariNairJHUAPL</id>
|
||||
<name>Hari Nair</name>
|
||||
<email>Hari.Nair@jhuapl.edu</email>
|
||||
<organization>JHUAPL</organization>
|
||||
<organizationUrl>https://www.jhuapl.edu/</organizationUrl>
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
<scm>
|
||||
<connection>scm:git:https://github.com/JHUAPL/Picante.git</connection>
|
||||
<url>https://github.com/JHUAPL/Picante.git</url>
|
||||
<developerConnection>scm:git:https://github.com/JHUAPL/Picante.git</developerConnection>
|
||||
<tag>HEAD</tag>
|
||||
</scm>
|
||||
|
||||
<!-- publish to maven central -->
|
||||
<distributionManagement>
|
||||
<snapshotRepository>
|
||||
<id>ossrh</id>
|
||||
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||
</snapshotRepository>
|
||||
</distributionManagement>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.11.0</version>
|
||||
<configuration>
|
||||
<release>17</release>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-enforcer-plugin</artifactId>
|
||||
<version>3.4.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>enforce-maven</id>
|
||||
<goals>
|
||||
<goal>enforce</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<rules>
|
||||
<requireMavenVersion>
|
||||
<version>3.2.5</version>
|
||||
</requireMavenVersion>
|
||||
</rules>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>3.3.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.1.2</version>
|
||||
<configuration>
|
||||
<testFailureIgnore>true</testFailureIgnore>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-enforcer-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>32.1.2-jre</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.easymock</groupId>
|
||||
<artifactId>easymock</artifactId>
|
||||
<version>5.2.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.13.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.jafama</groupId>
|
||||
<artifactId>jafama</artifactId>
|
||||
<version>2.3.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-math3</artifactId>
|
||||
<version>3.6.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.locationtech.jts</groupId>
|
||||
<artifactId>jts-core</artifactId>
|
||||
<version>1.19.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,53 @@
|
||||
package picante.collections;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import com.google.common.collect.UnmodifiableIterator;
|
||||
|
||||
/**
|
||||
* Simple abstract class that implements the {@link Iterator} interface from a source that is
|
||||
* organized by index. Simply supply implementations for the two abstract methods and you'll have an
|
||||
* {@link Iterator} that works properly.
|
||||
*
|
||||
* @param <E> the element over which iteration is to be performed
|
||||
*/
|
||||
public abstract class AbstractIndexedIterator<E> extends UnmodifiableIterator<E> {
|
||||
|
||||
private int index = 0;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return index < elements();
|
||||
}
|
||||
|
||||
@Override
|
||||
public E next() {
|
||||
if (index < elements()) {
|
||||
/*
|
||||
* Post-increment index after retrieval.
|
||||
*/
|
||||
return element(index++);
|
||||
}
|
||||
throw new NoSuchElementException("Unable to access element at index: " + index
|
||||
+ " from indexed elements of length: " + elements());
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide the number of elements in the indexed list.
|
||||
*
|
||||
* @return the number
|
||||
*/
|
||||
protected abstract int elements();
|
||||
|
||||
/**
|
||||
* Supplies the element associated with a particular index in the range [0, elements()-1]
|
||||
* respectively
|
||||
*
|
||||
* @param index the index of interest
|
||||
*
|
||||
* @return the element at index
|
||||
*/
|
||||
protected abstract E element(int index);
|
||||
|
||||
}
|
||||
26
src/main/java/picante/collections/AbstractReadOnlyList.java
Normal file
26
src/main/java/picante/collections/AbstractReadOnlyList.java
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Filename: AbstractReadOnlyList.java Author : vandejd1 Created : Feb 24, 2009
|
||||
*
|
||||
* Copyright (C) 2008 The Johns Hopkins University Applied Physics Laboratory (JHU/APL) All rights
|
||||
* reserved
|
||||
*/
|
||||
package picante.collections;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.RandomAccess;
|
||||
|
||||
|
||||
/**
|
||||
* a base class that allows you to easily get a {@link List} view of objects
|
||||
*
|
||||
* all add or modify methods throw runtime exceptions
|
||||
*
|
||||
* this version implements the marker interface RandomAccess, so make sure your implementation
|
||||
* suppoorts this (but the only consequence is that Java collection utilities will be slow).
|
||||
*
|
||||
* @author vandejd1
|
||||
*/
|
||||
public abstract class AbstractReadOnlyList<T> extends AbstractSequentialReadOnlyList<T>
|
||||
implements RandomAccess {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
package picante.collections;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/**
|
||||
* abstract base class for making a list view of objects; you just need the size() and get(i)
|
||||
* methods;
|
||||
*
|
||||
* Note that this list does not implement the marker interface RandomAccess, meaning that the
|
||||
* collections will not assume fast random access. (There is a subclass that does have random access
|
||||
* if that's what you want.)
|
||||
*
|
||||
* All methods that add or modify elements throw runtime exceptions.
|
||||
*
|
||||
* @author vandejd1
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public abstract class AbstractSequentialReadOnlyList<T> implements List<T> {
|
||||
|
||||
@Override
|
||||
public boolean add(T o) {
|
||||
throw new UnsupportedOperationException("Unsupported method called on a read-only list.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(int index, T element) {
|
||||
throw new UnsupportedOperationException("Unsupported method called on a read-only list.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends T> c) {
|
||||
throw new UnsupportedOperationException("Unsupported method called on a read-only list.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(int index, Collection<? extends T> c) {
|
||||
throw new UnsupportedOperationException("Unsupported method called on a read-only list.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
throw new UnsupportedOperationException("Unsupported method called on a read-only list.");
|
||||
}
|
||||
|
||||
/**
|
||||
* a very simple implementation that does linear search
|
||||
*/
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
for (int i = 0; i < size(); i++) {
|
||||
if (get(i).equals(o)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* does linear search on every element in the list
|
||||
*/
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> c) {
|
||||
for (Object o : c) {
|
||||
if (!contains(o)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* a very simple implementation that does linear search to find the index of the element that
|
||||
* ".equals() the given object
|
||||
*/
|
||||
@Override
|
||||
public int indexOf(Object o) {
|
||||
for (int i = 0; i < size(); i++) {
|
||||
if (get(i).equals(o)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return size() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return listIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int lastIndexOf(Object o) {
|
||||
throw new UnsupportedOperationException("Unsupported method called on a read-only list.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListIterator<T> listIterator() {
|
||||
return listIterator(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListIterator<T> listIterator(int index) {
|
||||
return new ListItr(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
throw new UnsupportedOperationException("Unsupported method called on a read-only list.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public T remove(int index) {
|
||||
throw new UnsupportedOperationException("Unsupported method called on a read-only list.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> c) {
|
||||
throw new UnsupportedOperationException("Unsupported method called on a read-only list.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> c) {
|
||||
throw new UnsupportedOperationException("Unsupported method called on a read-only list.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public T set(int index, T element) {
|
||||
throw new UnsupportedOperationException("Unsupported method called on a read-only list.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<T> subList(int fromIndex, int toIndex) {
|
||||
// TODO: implement this?
|
||||
throw new UnsupportedOperationException("Unsupported method called on a read-only list.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
// TODO: implement this?
|
||||
throw new UnsupportedOperationException("Unsupported method called on a read-only list.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public <X> X[] toArray(X[] a) {
|
||||
// TODO: implement this?
|
||||
throw new UnsupportedOperationException("Unsupported method called on a read-only list.");
|
||||
}
|
||||
|
||||
private class Itr implements Iterator<T> {
|
||||
/**
|
||||
* Index of element to be returned by subsequent call to next.
|
||||
*/
|
||||
int cursor = 0;
|
||||
|
||||
/**
|
||||
* Index of element returned by most recent call to next or previous. Reset to -1 if this
|
||||
* element is deleted by a call to remove.
|
||||
*/
|
||||
int lastRet = -1;
|
||||
|
||||
// /**
|
||||
// * The modCount value that the iterator believes that the backing
|
||||
// * List should have. If this expectation is violated, the iterator
|
||||
// * has detected concurrent modification.
|
||||
// */
|
||||
// int expectedModCount = modCount;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return cursor != size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
// checkForComodification();
|
||||
try {
|
||||
T next = get(cursor);
|
||||
lastRet = cursor++;
|
||||
return next;
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
// checkForComodification();
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException("Unsupported method called on a read-only list.");
|
||||
}
|
||||
|
||||
// final void checkForComodification()
|
||||
// {
|
||||
// if (modCount != expectedModCount)
|
||||
// throw new ConcurrentModificationException();
|
||||
// }
|
||||
}
|
||||
|
||||
private class ListItr extends Itr implements ListIterator<T> {
|
||||
ListItr(int index) {
|
||||
cursor = index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPrevious() {
|
||||
return cursor != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T previous() {
|
||||
// checkForComodification();
|
||||
try {
|
||||
int i = cursor - 1;
|
||||
T previous = get(i);
|
||||
lastRet = cursor = i;
|
||||
return previous;
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
// checkForComodification();
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextIndex() {
|
||||
return cursor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int previousIndex() {
|
||||
return cursor - 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(T o) {
|
||||
throw new UnsupportedOperationException("Unsupported method called on a read-only list.");
|
||||
// if (lastRet == -1)
|
||||
// throw new IllegalStateException();
|
||||
// // checkForComodification();
|
||||
//
|
||||
// try {
|
||||
// AbstractList.this.set(lastRet, o);
|
||||
// expectedModCount = modCount;
|
||||
// } catch (IndexOutOfBoundsException e) {
|
||||
// throw new ConcurrentModificationException();
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(T o) {
|
||||
throw new UnsupportedOperationException("Unsupported method called on a read-only list.");
|
||||
// checkForComodification();
|
||||
//
|
||||
// try {
|
||||
// AbstractList.this.add(cursor++, o);
|
||||
// lastRet = -1;
|
||||
// expectedModCount = modCount;
|
||||
// } catch (IndexOutOfBoundsException e) {
|
||||
// throw new ConcurrentModificationException();
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
462
src/main/java/picante/collections/ArrayUtilities.java
Normal file
462
src/main/java/picante/collections/ArrayUtilities.java
Normal file
@@ -0,0 +1,462 @@
|
||||
package picante.collections;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* This class consists exclusively of static methods that operate on or return arrays. It is much
|
||||
* like the Arrays class in the java.util package, only it provides other useful methods.
|
||||
*
|
||||
* TODO: Flush out the class with the remaining primitive array search methods
|
||||
*
|
||||
*/
|
||||
public class ArrayUtilities {
|
||||
|
||||
/**
|
||||
* Determine the index of the last element less than or equal to the specified key. The list must
|
||||
* be sorted into ascending order {@link Arrays#sort(double[])}. If it is not sorted, the results
|
||||
* are undefined. If the list contains multiple elements equal to the specified one, the last one
|
||||
* is guaranteed to be extracted.
|
||||
* <p>
|
||||
* The method utilizes {@link Arrays#binarySearch(double[], int, int, double)} so its performance
|
||||
* characteristics are closely related.
|
||||
* </p>
|
||||
*
|
||||
* @param list the list containing sorted doubles over which to search
|
||||
* @param key the key to locate the last element less than or equal to in the list
|
||||
*
|
||||
* @return the range returned is [-1,list.length-1], where -1 indicates that key precedes all
|
||||
* elements in the list.
|
||||
*/
|
||||
public static int lastLessThanOrEqualTo(double[] list, double key) {
|
||||
return lastLessThanOrEqualTo(list, 0, list.length, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the index of the last element less than or equal to the specified key. The list must
|
||||
* be sorted into ascending order {@link Arrays#sort(double[])}. If it is not sorted, the results
|
||||
* are undefined. If the list contains multiple elements equal to the specified key value, the
|
||||
* last one is guaranteed to be extracted.
|
||||
* <p>
|
||||
* The method utilizes {@link Arrays#binarySearch(double[], int, int, double)} so its performance
|
||||
* characteristics are closely related.
|
||||
* </p>
|
||||
*
|
||||
* @param list the list over which a subset of values is to be searched
|
||||
* @param startIndex the starting position to perform the search
|
||||
* @param length the length over which to perform the search
|
||||
* @param key the key to locate
|
||||
*
|
||||
* @return the index of the element last less than or equal to the supplied key in the sublist.
|
||||
* The range returned is [pos-1,pos+length-1] where pos-1 indicates that key precedes all
|
||||
* elements in the specified range within list.
|
||||
*/
|
||||
public static int lastLessThanOrEqualTo(double[] list, int startIndex, int length, double key) {
|
||||
int result = Arrays.binarySearch(list, startIndex, startIndex + length, key);
|
||||
|
||||
/*
|
||||
* If the result was located in the list directly, locate the last element equal to it and
|
||||
* return that index.
|
||||
*/
|
||||
if (result >= 0) {
|
||||
int lastEqualIndex = locateLastElementEqualTo(result, list, startIndex + length);
|
||||
return lastEqualIndex;
|
||||
}
|
||||
|
||||
return convertIndexForLessThan(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the index of the last element strictly less than the specified key. The list must be
|
||||
* sorted into ascending order {@link Arrays#sort(double[])}. If it is not sorted, the results are
|
||||
* undefined. If the list contains multiple equal elements to the specified one, the last one is
|
||||
* guaranteed to be extracted.
|
||||
* <p>
|
||||
* This method utilizes {@link Arrays#binarySearch(double[], int, int, double)} so its performance
|
||||
* characteristics are closely related.
|
||||
* </p>
|
||||
*
|
||||
* @param list the list containing sorted doubles over which to search
|
||||
* @param key the key to locate the last element strictly less than in the list
|
||||
*
|
||||
* @return the range returned is [-1, list.length-1], where -1 indicates that key precedes all
|
||||
* elements in the list.
|
||||
*/
|
||||
public static int lastLessThan(double[] list, double key) {
|
||||
return lastLessThan(list, 0, list.length, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the index of the last element strictly less than the specified key. The list must be
|
||||
* sorted into ascending order {@link Arrays#sort(double[])}. If it is not sorted, the results are
|
||||
* undefined. If the list contains multiple elements equal to the specified key value, the last
|
||||
* one is guaranteed to be extracted.
|
||||
* <p>
|
||||
* This method utilizes {@link Arrays#binarySearch(double[], int, int, double)} so its performance
|
||||
* characteristics are closely related.
|
||||
* </p>
|
||||
*
|
||||
* @param list the list over which a subset of values is to be searched
|
||||
* @param startIndex the starting position from which to perform the search
|
||||
* @param length the length over which to perform the search
|
||||
* @param key the key to locate
|
||||
*
|
||||
* @return the index of the last element strictly less than the supplied key in the sublist. The
|
||||
* range returned is [pos-1,pos+length-1] where pos-1 indicates that key preceds all
|
||||
* elements in the specified range within the list.
|
||||
*/
|
||||
public static int lastLessThan(double[] list, int startIndex, int length, double key) {
|
||||
int result = Arrays.binarySearch(list, startIndex, startIndex + length, key);
|
||||
|
||||
/*
|
||||
* If the result was located in the list directly, locate the first element equal to it and
|
||||
* return that index less 1.
|
||||
*/
|
||||
if (result >= 0) {
|
||||
int lastEqualIndex = locateFirstElementEqualTo(result, list, startIndex);
|
||||
return lastEqualIndex - 1;
|
||||
}
|
||||
|
||||
return convertIndexForLessThan(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the index of the first element greater than or equal to the specified key. The list
|
||||
* must be sorted into ascending order {@link Arrays#sort(double[])}. If it is not sorted, the
|
||||
* results are undefined. If the list contains multiple elements equal to the specified key, the
|
||||
* first one is guaranteed to be located.
|
||||
* <p>
|
||||
* This method utilizes {@link Arrays#binarySearch(double[], int, int, double)} so its performance
|
||||
* characteristics are closely related.
|
||||
* </p>
|
||||
*
|
||||
* @param list the sorted list containing the values to be searched
|
||||
* @param key the key to locate
|
||||
*
|
||||
* @return the range returned is [0, list.length], where list.length indicates that key follows
|
||||
* all the elements in the list.
|
||||
*/
|
||||
public static int firstGreaterThanOrEqualTo(double[] list, double key) {
|
||||
|
||||
/*
|
||||
* This is just as simple as invoking the corresponding lastLessThan method and adding one.
|
||||
*/
|
||||
return lastLessThan(list, key) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the index of the first element greater than or equal to the specified key in the
|
||||
* range within the supplied list. The range must be sorted into ascending order
|
||||
* {@link Arrays#sort(double[], int, int)}. If it is not sorted, the results are undefined. If the
|
||||
* list contains multiple elements equal to the specified key, the first one is guaranteed to be
|
||||
* located.
|
||||
* <p>
|
||||
* This method utilizes {@link Arrays#binarySearch(double[], int, int, double)} so its performance
|
||||
* characteristics are closely related.
|
||||
* </p>
|
||||
*
|
||||
* @param list the list from which the range is to be searched
|
||||
* @param startIndex the start index of the search range
|
||||
* @param length the length of the search range
|
||||
* @param key the key to locate
|
||||
*
|
||||
* @return the range returned is [pos, pos+length], where pos+length indicates that key follows
|
||||
* all elements in the range.
|
||||
*/
|
||||
public static int firstGreaterThanOrEqualTo(double[] list, int startIndex, int length,
|
||||
double key) {
|
||||
|
||||
/*
|
||||
* This is just as simple as invoking the corresponding lastLessThan method and adding one.
|
||||
*/
|
||||
return lastLessThan(list, startIndex, length, key) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the index of the first element strictly greater than the specified key. The list must
|
||||
* be sorted into ascending order: {@link Arrays#sort(double[])}. If it is not sorted, the results
|
||||
* are undefined. If the list contains multiple elements equal to the specified object, the first
|
||||
* one is guaranteed to be located.
|
||||
* <p>
|
||||
* This method utilizes {@link Arrays#binarySearch(double[], int, int, double)} so its performance
|
||||
* characteristics are closely related.
|
||||
* </p>
|
||||
*
|
||||
* @param list the list over which to perform the search
|
||||
* @param key the value to locate
|
||||
*
|
||||
* @return the range returned is [0, list.length], where list.length indicates that key follows
|
||||
* all elements in the list.
|
||||
*/
|
||||
public static int firstGreaterThan(double[] list, double key) {
|
||||
/*
|
||||
* This is just as simple as invoking the corresponding lastLessThanOrEqualTo method and adding
|
||||
* one.
|
||||
*/
|
||||
return lastLessThanOrEqualTo(list, key) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the index of the first element strictly greater than the specified key within the
|
||||
* specified range of list. The range must be sorted into ascending order:
|
||||
* {@link Arrays#sort(double[], int, int)}. If it is not sorted, the results are undefined. If the
|
||||
* list contains multiple elements equal to the specified key, the first one in the range is
|
||||
* guaranteed to be located.
|
||||
* <p>
|
||||
* This method utilizes {@link Arrays#binarySearch(double[], int, int, double)} so its performance
|
||||
* characteristics are closely related.
|
||||
* </p>
|
||||
*
|
||||
* @param list the list from which the range is to be searched
|
||||
* @param startIndex the start index of the search range
|
||||
* @param length the length of the search range
|
||||
* @param key the key to locate
|
||||
*
|
||||
* @return the range returned is [pos, pos+length], where pos+length indicates that key follows
|
||||
* all elements in the range
|
||||
*/
|
||||
public static int firstGreaterThan(double[] list, int startIndex, int length, double key) {
|
||||
|
||||
/*
|
||||
* This is just as simple as invoking the corresponding lastLessThanOrEqualTo method and adding
|
||||
* one.
|
||||
*/
|
||||
return lastLessThanOrEqualTo(list, startIndex, length, key) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Locate the last element in the list equal to the element by the supplied index.
|
||||
*
|
||||
* @param index the index at which to start the search. It should be an index to an existing
|
||||
* element of the list that is of interest.
|
||||
* @param list the list over which the search is to be performed
|
||||
* @param maxIndex the maximum index which is to be considered
|
||||
*
|
||||
* @return the index of the last element equal to list[index] in the sorted list.
|
||||
*/
|
||||
private static int locateLastElementEqualTo(int index, double[] list, int maxIndex) {
|
||||
|
||||
/*
|
||||
* If we are already at the end of the list, just return.
|
||||
*/
|
||||
if (index == list.length - 1) {
|
||||
return index;
|
||||
}
|
||||
|
||||
int result = index;
|
||||
|
||||
/*
|
||||
* Loop over elements in the list incrementing result as long as they are equal to list[index].
|
||||
*/
|
||||
while ((result < maxIndex) && (list[++result] == list[index])) {
|
||||
}
|
||||
|
||||
/*
|
||||
* Result will have been incremented one past the desired index, decrement it.
|
||||
*/
|
||||
return --result;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Locate the first element in the list equal to the element at position index.
|
||||
*
|
||||
* @param index the index at which to start the search for the first value.
|
||||
* @param list the list over which the search is to be performed.
|
||||
* @param firstIndex the minimum index over which the search is to happen
|
||||
*
|
||||
* @return the index of the first element equal to list[index] in the sorted list.
|
||||
*/
|
||||
private static int locateFirstElementEqualTo(int index, double[] list, int firstIndex) {
|
||||
|
||||
/*
|
||||
* If we are already at the start of the range, just return.
|
||||
*/
|
||||
if (index == firstIndex) {
|
||||
return index;
|
||||
}
|
||||
|
||||
int result = index;
|
||||
|
||||
/*
|
||||
* Loop over the previous elements in the list as long as they continue to compare with equality
|
||||
* to the element at list[index].
|
||||
*/
|
||||
while ((result > firstIndex) && (list[--result] == list[index])) {
|
||||
}
|
||||
|
||||
/*
|
||||
* Result will have been decremented one past the desired index, increment it.
|
||||
*/
|
||||
return ++result;
|
||||
}
|
||||
|
||||
/**
|
||||
* In the event that the binarySearch algorithm from the Collections class is unable to turn up a
|
||||
* matched element, locate the index of the element that is strictly less than the one sought.
|
||||
*
|
||||
* @param result a negative result from either {@link Arrays#binarySearch(byte[], byte)}
|
||||
*
|
||||
* @param listSize the size of the list over which the binary search was performed at the time of
|
||||
* the search
|
||||
*
|
||||
* @return the index of the element strictly less than the one sought after in the binary search.
|
||||
*/
|
||||
private static int convertIndexForLessThan(int result) {
|
||||
|
||||
int insertionPoint = -result - 1;
|
||||
|
||||
/*
|
||||
* Since the insertion point is defined as the index at which everyone will be shifted to the
|
||||
* right:
|
||||
*
|
||||
* value: 10 20 30 40 index: 0 1 2 3
|
||||
*
|
||||
* If insertion point is 1, then that means the result of inserting this element into the list
|
||||
* will result:
|
||||
*
|
||||
* value: 10 15 20 30 40 index: 0 1 2 3 4
|
||||
*
|
||||
* would be the result, and 15 would have reduced to an insertion point of 1. So, long story
|
||||
* short, if we reach here then just subtract one and we'll get the answer we desire.
|
||||
*/
|
||||
return insertionPoint - 1;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an array is ragged, e.g:
|
||||
*
|
||||
* <pre>
|
||||
* This is a ragged array
|
||||
* { {1, 2, 3} }
|
||||
* { {3, 4} }
|
||||
*
|
||||
* This is not a ragged array
|
||||
* { {1, 2, 3} }
|
||||
* { {3, 4, 5} }
|
||||
* </pre>
|
||||
*
|
||||
* @param r input array
|
||||
* @param <R> the array type parameter
|
||||
*
|
||||
* @return true if the input is a ragged array, false if it is 'rectangular'
|
||||
*/
|
||||
public static <R> boolean isRagged(R[][] r) {
|
||||
|
||||
if (r.length > 0) {
|
||||
|
||||
int width0 = r[0].length;
|
||||
|
||||
for (int i = 0; i < r.length; i++) {
|
||||
|
||||
if (r[i].length != width0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an array is ragged.
|
||||
*
|
||||
* @param r input array
|
||||
* @param <R> the array type parameter
|
||||
*
|
||||
* @return true if the input is a ragged array, false if it is 'rectangular'
|
||||
*/
|
||||
public static <R> boolean isRagged(R[][][] r) {
|
||||
|
||||
if (r.length > 0 && r[0].length > 0) {
|
||||
|
||||
int width0 = r[0].length;
|
||||
int depth0 = r[0][0].length;
|
||||
|
||||
for (int i = 0; i < r.length; i++) {
|
||||
|
||||
if (r[i].length != width0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int j = 0; j < r[0].length; j++) {
|
||||
if (r[i][j].length != depth0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an array is ragged, e.g:
|
||||
*
|
||||
* <pre>
|
||||
* This is a ragged array
|
||||
* { {1, 2, 3} }
|
||||
* { {3, 4} }
|
||||
*
|
||||
* This is not a ragged array
|
||||
* { {1, 2, 3} }
|
||||
* { {3, 4, 5} }
|
||||
* </pre>
|
||||
*
|
||||
* @param r input array
|
||||
* @return true if the input is a ragged array, false if it is 'rectangular'
|
||||
*/
|
||||
public static boolean isRagged(double[][] r) {
|
||||
|
||||
if (r.length > 0) {
|
||||
|
||||
int width0 = r[0].length;
|
||||
|
||||
for (int i = 0; i < r.length; i++) {
|
||||
|
||||
if (r[i].length != width0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an array is ragged.
|
||||
*
|
||||
* @param r input array
|
||||
* @return true if the input is a ragged array, false if it is 'rectangular'
|
||||
*/
|
||||
public static boolean isRagged(double[][][] r) {
|
||||
|
||||
if (r.length > 0 && r[0].length > 0) {
|
||||
|
||||
int width0 = r[0].length;
|
||||
int depth0 = r[0][0].length;
|
||||
|
||||
for (int i = 0; i < r.length; i++) {
|
||||
|
||||
if (r[i].length != width0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int j = 0; j < r[0].length; j++) {
|
||||
if (r[i][j].length != depth0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
534
src/main/java/picante/collections/CollectionUtilities.java
Normal file
534
src/main/java/picante/collections/CollectionUtilities.java
Normal file
@@ -0,0 +1,534 @@
|
||||
package picante.collections;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
/**
|
||||
* This class consists exclusively of static methods that operate on or return collections. It
|
||||
* contains polymorphic algorithms that operate on collections. It is much like the Collections
|
||||
* class in the java.util package, only it provides other useful generic methods.
|
||||
*
|
||||
* The methods of this class all throw a NullPointerException if the collections or class objects
|
||||
* provided to them are null.
|
||||
*/
|
||||
public class CollectionUtilities {
|
||||
|
||||
/**
|
||||
* Determine the index of the last element less than or equal to the specified key. The list must
|
||||
* be sorted into ascending order according to the natural ordering specified by the
|
||||
* implementation of the Comparable interface. If it is not sorted, the results are undefined. If
|
||||
* the list contains multiple elements equal to the specified object, the last one is guaranteed
|
||||
* to be extracted.
|
||||
*
|
||||
* The method utilizes {@link java.util.Collections#binarySearch(List, Object)} so its performance
|
||||
* characteristics are closely related.
|
||||
*
|
||||
* @param <T> the parameterized type of the key
|
||||
* @param list the list containing objects capable of being compared to an instance of T, sorted
|
||||
* in an order consistent with the comparison
|
||||
* @param key the key to locate the last element less than or equal to in the list
|
||||
*
|
||||
* @return the range returned is [-1,list.size()-1], where -1 indicates that key precedes all
|
||||
* elements in the list.
|
||||
*/
|
||||
public static <T> int lastLessThanOrEqualTo(List<? extends Comparable<? super T>> list, T key) {
|
||||
|
||||
int result = Collections.binarySearch(list, key);
|
||||
|
||||
/*
|
||||
* If the result was located in the list directly, locate the last element equal to it and
|
||||
* return that index.
|
||||
*/
|
||||
if (result >= 0) {
|
||||
int lastEqualIndex = locateLastElementEqualTo(result, key, list);
|
||||
return lastEqualIndex;
|
||||
}
|
||||
|
||||
return convertIndexForLessThan(result, list.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the index of the last element less than or equal to the specified key. The list must
|
||||
* be sorted into ascending order according to the ordering specified by the implementation of the
|
||||
* supplied Comparator. If it is not sorted, the results are undefined. If the list contains
|
||||
* multiple elements equal to the specified object, the last one is guaranteed to be extracted.
|
||||
*
|
||||
* The method utilizes {@link java.util.Collections#binarySearch(List, Object, Comparator)} so its
|
||||
* performance characteristics are closely related.
|
||||
*
|
||||
* @param <T> the parameterized type of the key
|
||||
* @param list the list containing objects to be compared against key with the supplied
|
||||
* comparator, sorted in an ordering consistent with the comparator
|
||||
* @param key the key to locate the last element less than or equal to in the list
|
||||
* @param c the comparator defining the ordering to be utilized in the search
|
||||
*
|
||||
* @return the range of the return values is [-1,list.size()-1], where -1 indicates that key
|
||||
* precedes all elements in the list.
|
||||
*/
|
||||
public static <T> int lastLessThanOrEqualTo(List<? extends T> list, T key,
|
||||
Comparator<? super T> c) {
|
||||
|
||||
int result = Collections.binarySearch(list, key, c);
|
||||
|
||||
/*
|
||||
* If the result was located in the list directly, locate the last element equal to it and
|
||||
* return that index.
|
||||
*/
|
||||
if (result >= 0) {
|
||||
int lastEqualIndex = locateLastElementEqualTo(result, list, c);
|
||||
return lastEqualIndex;
|
||||
}
|
||||
|
||||
return convertIndexForLessThan(result, list.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the index of the last element less than the specified key. The list must be sorted
|
||||
* into ascending order according to the natural ordering specified by the implementation of the
|
||||
* Comparable interface. If it is not sorted, the results are undefined. If the list contains
|
||||
* multiple elements equal to the specified object, the last one is guaranteed to be extracted.
|
||||
*
|
||||
* The method utilizes {@link java.util.Collections#binarySearch(List, Object)} so its performance
|
||||
* characteristics are closely related.
|
||||
*
|
||||
* @param <T> the parameterized type of the key
|
||||
* @param list the list containing objects capable of being compared to an instance of T, sorted
|
||||
* in an order consistent with the comparison
|
||||
* @param key the key to locate the last element less than in the list
|
||||
*
|
||||
* @return the range returned is [-1,list.size()-1], where -1 indicates that key precedes all
|
||||
* elements in the list.
|
||||
*/
|
||||
public static <T> int lastLessThan(List<? extends Comparable<? super T>> list, T key) {
|
||||
|
||||
int result = Collections.binarySearch(list, key);
|
||||
|
||||
/*
|
||||
* If the result was located in the list directly, locate the first element equal to it and
|
||||
* return that index less 1.
|
||||
*/
|
||||
if (result >= 0) {
|
||||
int lastEqualIndex = locateFirstElementEqualTo(result, key, list);
|
||||
return lastEqualIndex - 1;
|
||||
}
|
||||
|
||||
return convertIndexForLessThan(result, list.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the index of the last element less than the specified key. The list must be sorted
|
||||
* into ascending order according to the ordering specified by the implementation of the supplied
|
||||
* Comparator. If it is not sorted, the results are undefined. If the list contains multiple
|
||||
* elements equal to the specified object, the last one is guaranteed to be extracted.
|
||||
*
|
||||
* The method utilizes {@link java.util.Collections#binarySearch(List, Object, Comparator)} so its
|
||||
* performance characteristics are closely related.
|
||||
*
|
||||
* @param <T> the parameterized type of the key
|
||||
* @param list the list containing objects to be compared against key with the supplied
|
||||
* comparator, sorted in an ordering consistent with the comparator
|
||||
* @param key the key to locate the last element less than in the list
|
||||
* @param c the comparator defining the ordering to be utilized in the search
|
||||
*
|
||||
* @return the range of the return values is [-1,list.size()-1], where -1 indicates that key
|
||||
* precedes all elements in the list.
|
||||
*/
|
||||
public static <T> int lastLessThan(List<? extends T> list, T key, Comparator<? super T> c) {
|
||||
|
||||
int result = Collections.binarySearch(list, key, c);
|
||||
|
||||
/*
|
||||
* If the result was located in the list directly, locate the first element equal to it and
|
||||
* return that index less 1.
|
||||
*/
|
||||
if (result >= 0) {
|
||||
int lastEqualIndex = locateFirstElementEqualTo(result, list, c);
|
||||
return lastEqualIndex - 1;
|
||||
}
|
||||
|
||||
return convertIndexForLessThan(result, list.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the index of the first element greater than or equal to the specified key. The list
|
||||
* must be sorted into ascending order according to the natural ordering specified by the
|
||||
* implementation of the Comparable interface. If it is not sorted, the results are undefined. If
|
||||
* the list contains multiple elements equal to the specified object, the first one is guaranteed
|
||||
* to be extracted.
|
||||
*
|
||||
* The method utilizes {@link java.util.Collections#binarySearch(List, Object)} so its performance
|
||||
* characteristics are closely related.
|
||||
*
|
||||
* @param <T> the parameterized type of the key
|
||||
* @param list the list containing objects capable of being compared to an instance of T, sorted
|
||||
* in an order consistent with the comparison
|
||||
* @param key the key to locate the first element greater than or equal to in the list
|
||||
*
|
||||
* @return the range returned is [0,list.size()], where list.size() indicates that key follows all
|
||||
* elements in the list.
|
||||
*/
|
||||
public static <T> int firstGreaterThanOrEqualTo(List<? extends Comparable<? super T>> list,
|
||||
T key) {
|
||||
|
||||
/*
|
||||
* This is just as simple as invoking the corresponding lastLessThan method and adding one.
|
||||
*/
|
||||
return lastLessThan(list, key) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the index of the first element greater than or equal to the specified key. The list
|
||||
* must be sorted into ascending order according to the ordering specified by the implementation
|
||||
* of the supplied Comparator. If it is not sorted, the results are undefined. If the list
|
||||
* contains multiple elements equal to the specified object, the first one is guaranteed to be
|
||||
* extracted.
|
||||
*
|
||||
* The method utilizes {@link java.util.Collections#binarySearch(List, Object, Comparator)} so its
|
||||
* performance characteristics are closely related.
|
||||
*
|
||||
* @param <T> the parameterized type of the key
|
||||
* @param list the list containing objects to be compared against key with the supplied
|
||||
* comparator, sorted in an ordering consistent with the comparator
|
||||
* @param key the key to locate the first element greater than or equal to in the list
|
||||
* @param c the comparator defining the ordering to be utilized in the search
|
||||
*
|
||||
* @return the range of the return values is [0,list.size()], where list.size() indicates that key
|
||||
* follows all elements in the list.
|
||||
*/
|
||||
public static <T> int firstGreaterThanOrEqualTo(List<? extends T> list, T key,
|
||||
Comparator<? super T> c) {
|
||||
|
||||
/*
|
||||
* This is just as simple as invoking the corresponding lastLessThan method and adding one.
|
||||
*/
|
||||
return lastLessThan(list, key, c) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the index of the first element greater than the specified key. The list must be
|
||||
* sorted into ascending order according to the natural ordering specified by the implementation
|
||||
* of the Comparable interface. If it is not sorted, the results are undefined. If the list
|
||||
* contains multiple elements equal to the specified object, the first one is guaranteed to be
|
||||
* extracted.
|
||||
*
|
||||
* The method utilizes {@link java.util.Collections#binarySearch(List, Object)} so its performance
|
||||
* characteristics are closely related.
|
||||
*
|
||||
* @param <T> the parameterized type of the key
|
||||
* @param list the list containing objects capable of being compared to an instance of T, sorted
|
||||
* in an order consistent with the comparison
|
||||
* @param key the key to locate the first element greater than in the list
|
||||
*
|
||||
* @return the range returned is [0,list.size()], where list.size() indicates that key follows all
|
||||
* elements in the list.
|
||||
*/
|
||||
public static <T> int firstGreaterThan(List<? extends Comparable<? super T>> list, T key) {
|
||||
|
||||
/*
|
||||
* This is just as simple as invoking the corresponding lastLessThanOrEqualTo method and adding
|
||||
* one.
|
||||
*/
|
||||
return lastLessThanOrEqualTo(list, key) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the index of the first element greater than the specified key. The list must be
|
||||
* sorted into ascending order according to the ordering specified by the implementation of the
|
||||
* supplied Comparator. If it is not sorted, the results are undefined. If the list contains
|
||||
* multiple elements equal to the specified object, the first one is guaranteed to be extracted.
|
||||
*
|
||||
* The method utilizes {@link java.util.Collections#binarySearch(List, Object, Comparator)} so its
|
||||
* performance characteristics are closely related.
|
||||
*
|
||||
* @param <T> the parameterized type of the key
|
||||
* @param list the list containing objects to be compared against key with the supplied
|
||||
* comparator, sorted in an ordering consistent with the comparator
|
||||
* @param key the key to locate the first element greater than in the list
|
||||
* @param c the comparator defining the ordering to be utilized in the search
|
||||
*
|
||||
* @return the range of the return values is [0,list.size()], where list.size() indicates that key
|
||||
* follows all elements in the list.
|
||||
*/
|
||||
public static <T> int firstGreaterThan(List<? extends T> list, T key, Comparator<? super T> c) {
|
||||
|
||||
/*
|
||||
* This is just as simple as invoking the corresponding lastLessThanOrEqualTo method and adding
|
||||
* one.
|
||||
*/
|
||||
return lastLessThanOrEqualTo(list, key, c) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Locate the first element equal to the supplied key, starting at index. This expectation is that
|
||||
* index is already pointing at an element of the list that is equal to the supplied key from the
|
||||
* implementation of the Comparable interface's perspective.
|
||||
*
|
||||
* Supplying the key is necessary in the event that the list itself implements Comparable of an
|
||||
* unrelated type, of which key is an instance of a subclass.
|
||||
*
|
||||
* @param <T> the parameterized type of key
|
||||
* @param index the index at which to start the search. It should be such that:
|
||||
* list.get(index).compareTo(key) == 0.
|
||||
* @param key the key to compare against for equality
|
||||
* @param list the list containing objects to be compared against key, sorted in an order
|
||||
* consistent with the natural ordering defined by Comparable
|
||||
*
|
||||
* @return the index of the first element in the list that is equal to the supplied key.
|
||||
*/
|
||||
private static <T> int locateFirstElementEqualTo(int index, T key,
|
||||
List<? extends Comparable<? super T>> list) {
|
||||
|
||||
/*
|
||||
* If we are already at the head of the list, then just return.
|
||||
*/
|
||||
if (index == 0) {
|
||||
return index;
|
||||
}
|
||||
|
||||
int result = index;
|
||||
|
||||
ListIterator<? extends Comparable<? super T>> iterator = list.listIterator(index);
|
||||
|
||||
/*
|
||||
* Loop over the previous elements as long as they continue to compare with equality to key,
|
||||
* subtracting values from result.
|
||||
*/
|
||||
while ((iterator.hasPrevious()) && (iterator.previous().compareTo(key) == 0)) {
|
||||
result--;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Locate the first element in the list equal to the element by the supplied index.
|
||||
*
|
||||
* @param <T> the parameterized type of key
|
||||
* @param index the index at which to start the search. It should be an index to an existing
|
||||
* element of the list.
|
||||
* @param list the list containing objects to be compared against key, sorted in an order
|
||||
* consistent with the natural ordering defined by Comparable
|
||||
* @param c the comparator used to search through the list
|
||||
*
|
||||
* @return the index of the first element in the list that is equal to the supplied key.
|
||||
*/
|
||||
private static <T> int locateFirstElementEqualTo(int index, List<? extends T> list,
|
||||
Comparator<? super T> c) {
|
||||
|
||||
/*
|
||||
* If we are already at the head of the list, then just return.
|
||||
*/
|
||||
if (index == 0) {
|
||||
return index;
|
||||
}
|
||||
|
||||
int result = index;
|
||||
|
||||
ListIterator<? extends T> iterator = list.listIterator(index);
|
||||
|
||||
T key = iterator.next();
|
||||
|
||||
/*
|
||||
* Back up the interator to its previous state. We only stepped forward to extract the
|
||||
* comparison element.
|
||||
*/
|
||||
iterator.previous();
|
||||
|
||||
/*
|
||||
* Loop over elements in the list, decrementing result, until we find an element that no longer
|
||||
* compares to key with equality.
|
||||
*/
|
||||
while ((iterator.hasPrevious()) && (c.compare(key, iterator.previous()) == 0)) {
|
||||
result--;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Locate the last element equal to the supplied key, starting at index. This expectation is that
|
||||
* index is already pointing at an element of the list that is equal to the supplied key from the
|
||||
* implementation of the Comparable interface's perspective.
|
||||
*
|
||||
* Supplying the key is necessary in the event that the list itself implements Comparable of an
|
||||
* unrelated type, of which key is an instance of a subclass.
|
||||
*
|
||||
* @param <T> the parameterized type of key
|
||||
* @param index the index at which to start the search. It should be such that:
|
||||
* list.get(index).compareTo(key) == 0.
|
||||
* @param key the key to compare against for equality
|
||||
* @param list the list containing objects to be compared against key, sorted in an order
|
||||
* consistent with the natural ordering defined by Comparable
|
||||
*
|
||||
* @return the index of the last element in the list that is equal to the supplied key.
|
||||
*/
|
||||
private static <T> int locateLastElementEqualTo(int index, T key,
|
||||
List<? extends Comparable<? super T>> list) {
|
||||
|
||||
/*
|
||||
* If we are already at the end of the list, just return.
|
||||
*/
|
||||
if (index == list.size() - 1) {
|
||||
return index;
|
||||
}
|
||||
|
||||
int result = index;
|
||||
|
||||
ListIterator<? extends Comparable<? super T>> iterator = list.listIterator(index + 1);
|
||||
|
||||
/*
|
||||
* Loop over elements in the list incrementing result as long as the elements compare with key
|
||||
* as equality.
|
||||
*/
|
||||
while ((iterator.hasNext()) && (iterator.next().compareTo(key) == 0)) {
|
||||
result++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Locate the last element in the list equal to the element by the supplied index.
|
||||
*
|
||||
* @param <T> the parameterized type of key
|
||||
* @param index the index at which to start the search. It should be an index to an existing
|
||||
* element of the list.
|
||||
* @param list the list containing objects to be compared against key, sorted in an order
|
||||
* consistent with the natural ordering defined by Comparable
|
||||
* @param c the comparator used to search through the list
|
||||
*
|
||||
* @return the index of the last element in the list that is equal to the supplied key.
|
||||
*/
|
||||
private static <T> int locateLastElementEqualTo(int index, List<? extends T> list,
|
||||
Comparator<? super T> c) {
|
||||
|
||||
/*
|
||||
* If we are already at the end of the list, just return.
|
||||
*/
|
||||
if (index == list.size() - 1) {
|
||||
return index;
|
||||
}
|
||||
|
||||
int result = index;
|
||||
|
||||
ListIterator<? extends T> iterator = list.listIterator(index);
|
||||
|
||||
T key = iterator.next();
|
||||
|
||||
/*
|
||||
* Loop over elements of the list, incrementing result, as long as these elements continue
|
||||
* comparing with key as equality.
|
||||
*/
|
||||
while ((iterator.hasNext() && (c.compare(key, iterator.next()) == 0))) {
|
||||
result++;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* In the event that the binarySearch algorithm from the Collections class is unable to turn up a
|
||||
* matched element, locate the index of the element that is strictly less than the one sought.
|
||||
*
|
||||
* @param result a negative result from either
|
||||
* {@link java.util.Collections#binarySearch(List, Object)} or
|
||||
* {@link java.util.Collections#binarySearch(List, Object, Comparator)}
|
||||
*
|
||||
* @param listSize the size of the list over which the binary search was performed at the time of
|
||||
* the search
|
||||
*
|
||||
* @return the index of the element strictly less than the one sought after in the binary search.
|
||||
* Range of returned values is [-1,listSize], where -1 indicates the value sought after
|
||||
* precedes all elements in the list.
|
||||
*/
|
||||
private static int convertIndexForLessThan(int result, @SuppressWarnings("unused") int listSize) {
|
||||
|
||||
int insertionPoint = -result - 1;
|
||||
|
||||
/*
|
||||
* Since the insertion point is defined as the index at which everyone will be shifted to the
|
||||
* right:
|
||||
*
|
||||
* value: 10 20 30 40 index: 0 1 2 3
|
||||
*
|
||||
* If insertion point is 1, then that means the result of inserting this element into the list
|
||||
* will result:
|
||||
*
|
||||
* value: 10 15 20 30 40 index: 0 1 2 3 4
|
||||
*
|
||||
* would be the result, and 15 would have reduced to an insertion point of 1. So, long story
|
||||
* short, if we reach here then just subtract one and we'll get the answer we desire.
|
||||
*/
|
||||
return insertionPoint - 1;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the contents of an iterable to the supplied collection.
|
||||
* <p>
|
||||
* Elements are added to the collection via the {@link Collection#add(Object)} method in the order
|
||||
* that the iterator generated from the supplied iterable produces them.
|
||||
* </p>
|
||||
*
|
||||
* @param <T> the element type of the iterable
|
||||
* @param iterable the iterable, producing elements of type T
|
||||
* @param collection the collection to receive elements of type T
|
||||
*
|
||||
* @return a reference to collection for the convenience of method chaining
|
||||
*/
|
||||
public static <T, C extends Collection<? super T>> C addAll(Iterable<T> iterable, C collection) {
|
||||
return addAll(iterable.iterator(), collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the contents of an iterator to the supplied collection.
|
||||
* <p>
|
||||
* Elements are added to the collection via the {@link Collection#add(Object)} method in the order
|
||||
* that the iterator produces them.
|
||||
* </p>
|
||||
*
|
||||
* @param <T> the element type of the iterator
|
||||
* @param <C> the collection type
|
||||
* @param iterable the iterator, producing elements of type T
|
||||
* @param collection the collection to receive elements of type T
|
||||
*
|
||||
* @return a reference to collection for the convenience of method chaining
|
||||
*/
|
||||
public static <T, C extends Collection<? super T>> C addAll(Iterator<T> iterator, C collection) {
|
||||
while (iterator.hasNext()) {
|
||||
collection.add(iterator.next());
|
||||
}
|
||||
return collection;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* allows an ImmutableList of children objects to be converted to an ImmutableList of parent
|
||||
* objects; for an ordinary Java List, this would be dangerous because of the ability to
|
||||
* potentially ADD diverse objects into the list. But since this method deals with an immutable
|
||||
* list, nothing can be added. Note that the requirement that S extends T is not needed - the
|
||||
* compiler will let this through even if S does not extend T, but that's probably something you
|
||||
* don't want to do, so the S extends T is there to remind you of this fact. As long as S extends
|
||||
* T, the casting that goes on inside this method is safe.
|
||||
*
|
||||
* @param <T> the type of the child
|
||||
* @param <S> the type of the parent
|
||||
* @param listOfChildren the children to convert
|
||||
*
|
||||
* @return the same list cast as a list of parent objects (type T)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T, S extends T> ImmutableList<T> convertToListOfSuperclass(
|
||||
ImmutableList<S> listOfChildren) {
|
||||
// recast as list of parent objects:
|
||||
return (ImmutableList<T>) listOfChildren;
|
||||
}
|
||||
|
||||
}
|
||||
109
src/main/java/picante/collections/IndexRange.java
Normal file
109
src/main/java/picante/collections/IndexRange.java
Normal file
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* Author : vandejd1 Created : Apr 19, 2010
|
||||
*
|
||||
* Copyright (C) 2010 The Johns Hopkins University Applied Physics Laboratory (JHU/APL) All rights
|
||||
* reserved
|
||||
*/
|
||||
package picante.collections;
|
||||
|
||||
/**
|
||||
* captures a range of indices specified by a low index and a high index; both end points are
|
||||
* considered to be included in the range
|
||||
*
|
||||
*
|
||||
* NOTES for future development: If you want to add methods, look at the picante.math.intervals
|
||||
* package and make sure that any method names you use are consistent with the (non-integer)
|
||||
* interval processing class there. After discussion, we decided that its probably easier to
|
||||
* duplicate code for the integer interval mechanisms (if we ever need it) rather than try to come
|
||||
* up with a convoluted framework for handling (unwritable and writable) intervals of multiple
|
||||
* types.
|
||||
*
|
||||
* If you want to make this class writable: create a new parent class that is unwritable:
|
||||
* UnwritableIndexRange, and then move the getter methods there. Then this class becomes the
|
||||
* writable versions and you can add the setters here.
|
||||
*
|
||||
* This class is one instance of a larger concept - that of index mapping or index translation (as
|
||||
* in sub-setting, or selecting a sub-range). There is already a class in the timeseries library
|
||||
* that will eventually be moved into crucible that this class will eventually implement:
|
||||
* IIndexTranslator.
|
||||
*
|
||||
* @author vandejd1
|
||||
*/
|
||||
public class IndexRange {
|
||||
private final int lowIndex;
|
||||
private final int highIndex;
|
||||
|
||||
public IndexRange(int lowIndexInclusive, int highIndexInclusive) {
|
||||
this.lowIndex = lowIndexInclusive;
|
||||
this.highIndex = highIndexInclusive;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the lower index in the range; this index value is included in the range
|
||||
*/
|
||||
public int getLowIndex() {
|
||||
return lowIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the upper index in the range; this index is included in the range
|
||||
*/
|
||||
public int getHighIndex() {
|
||||
return highIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the total number of indices in the range, including the lower and upper endpoints
|
||||
*/
|
||||
public int getNumIndicesIncluded() {
|
||||
return highIndex - lowIndex + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns true if the given index is inside this range, i.e., if
|
||||
* {@code lowIndex <= idx <= highIndex}
|
||||
*
|
||||
* @param idx the index to test
|
||||
*
|
||||
* @return true if contained in the range
|
||||
*/
|
||||
public boolean contains(int idx) {
|
||||
return (idx >= lowIndex) && (idx <= highIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + highIndex;
|
||||
result = prime * result + lowIndex;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
IndexRange other = (IndexRange) obj;
|
||||
if (highIndex != other.highIndex) {
|
||||
return false;
|
||||
}
|
||||
if (lowIndex != other.lowIndex) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "IndexRange [lowIndex=" + lowIndex + ", highIndex=" + highIndex + "]";
|
||||
}
|
||||
|
||||
}
|
||||
19
src/main/java/picante/collections/package-info.java
Normal file
19
src/main/java/picante/collections/package-info.java
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Package containing Java collections framework related software.
|
||||
* <p>
|
||||
* This package simply contains any additional capabilities that are supplied and designed to
|
||||
* integrate with or into the general collections framework provided by Sun.
|
||||
* </p>
|
||||
* <p>
|
||||
* Features include the following:
|
||||
* <ul>
|
||||
* <li>Indexed based searching, namely utilities like locate the element last less than or equal to
|
||||
* an element in an existing list.</li>
|
||||
* <li>Flattening iterators, that turn various combinations of iterators and iterables of iterators
|
||||
* and iterables into a single flat iterator.</li>
|
||||
* <li>Filtering iterators, that take an iterator and a filter and produce an iterator that only
|
||||
* presents elements that pass the filter.</li>
|
||||
* <ul>
|
||||
* </p>
|
||||
*/
|
||||
package picante.collections;
|
||||
@@ -0,0 +1,83 @@
|
||||
package picante.data.list;
|
||||
|
||||
/**
|
||||
* A layer of abstraction to manage the {@link IndexOutOfBoundsException} generated by the record
|
||||
* retrieval routines in the record table interface.
|
||||
* <p>
|
||||
* This class will manage the generation of index out of bounds exceptions, blocking access to
|
||||
* invoking the {@link AbstractFixedLengthGaugedRetrievableWithExceptions#obtainRecord(int, Object)}
|
||||
* or {@link AbstractFixedLengthGaugedRetrievableWithExceptions#obtainTime(int)} methods if the
|
||||
* supplied index lies outside the specified range. It is simply a convenience class, if you do not
|
||||
* wish to manage the error handling in your own code.
|
||||
* </p>
|
||||
*
|
||||
* @param <R> the type of the data record required by the implementation
|
||||
*/
|
||||
public abstract class AbstractFixedLengthGaugedRetrievableWithExceptions<R>
|
||||
extends AbstractGaugedRetrievable<R> {
|
||||
|
||||
protected final int length;
|
||||
|
||||
public AbstractFixedLengthGaugedRetrievableWithExceptions(int length) {
|
||||
this.length = length;
|
||||
|
||||
if (length <= 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"Length: " + length + " is invalid. Length must be strictly positive.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getGauge(int index) {
|
||||
|
||||
if ((index < 0) || (index > (length - 1))) {
|
||||
throw new IndexOutOfBoundsException("The requested record index: " + index
|
||||
+ " is invalid. Indices must lie in [0," + (length - 1));
|
||||
}
|
||||
|
||||
return obtainTime(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to actually retrieve the time associated with a particular index. This is used directly
|
||||
* in the implementation of
|
||||
* {@link AbstractFixedLengthGaugedRetrievableWithExceptions#getGauge(int)} to actually read the
|
||||
* record.
|
||||
*
|
||||
* @param index index of interest
|
||||
*
|
||||
* @return the double capturing the associated time
|
||||
*/
|
||||
protected abstract double obtainTime(int index);
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public R get(int index, R buffer) {
|
||||
|
||||
if ((index < 0) || (index > (length - 1))) {
|
||||
throw new IndexOutOfBoundsException("The requested record index: " + index
|
||||
+ " is invalid. Indices must lie in [0," + (length - 1));
|
||||
}
|
||||
|
||||
return obtainRecord(index, buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to actually retrieve the record associated with a particular index. This is used
|
||||
* directly in the implementation of
|
||||
* {@link AbstractFixedLengthGaugedRetrievableWithExceptions#get(int, Object)} to retrieve the
|
||||
* record.
|
||||
*
|
||||
* @param index index of interest
|
||||
* @param buffer buffer to receive the record
|
||||
*
|
||||
* @return reference to buffer for convenience
|
||||
*/
|
||||
protected abstract R obtainRecord(int index, R buffer);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package picante.data.list;
|
||||
|
||||
/**
|
||||
* A layer of abstraction to manage the {@link IndexOutOfBoundsException} generated by the record
|
||||
* retrieval routines in the record list interface.
|
||||
* <p>
|
||||
* This class will manage the generation of index out of bounds exceptions, blocking access to
|
||||
* invoking the {@link AbstractFixedLengthGaugedRetrievableWithExceptions#obtainRecord(int, Object)}
|
||||
* method if the supplied index lies outside the specified range. It is simply a convenience class,
|
||||
* if you do not wish to manage the error handling in your own code.
|
||||
* </p>
|
||||
*
|
||||
* @param <R> the type of data record required by the implementation
|
||||
*/
|
||||
public abstract class AbstractFixedLengthRetrievableWithExceptions<R> implements Retrievable<R> {
|
||||
|
||||
protected final int length;
|
||||
|
||||
public AbstractFixedLengthRetrievableWithExceptions(int length) {
|
||||
this.length = length;
|
||||
|
||||
if (length <= 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"Length: " + length + " is invalid. Length must be strictly positive.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public R get(int index, R buffer) {
|
||||
|
||||
if ((index < 0) || (index > (length - 1))) {
|
||||
throw new IndexOutOfBoundsException("The requested record index: " + index
|
||||
+ " is invalid. Indices must lie in [0," + (length - 1));
|
||||
}
|
||||
|
||||
return obtainRecord(index, buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to actually retrieve the record associated with a particular index. This is used
|
||||
* directly in the implementation of
|
||||
* {@link AbstractFixedLengthGaugedRetrievableWithExceptions#get(int, Object)} to retrieve the
|
||||
* record.
|
||||
*
|
||||
* @param index index of interest
|
||||
* @param buffer buffer to receive the record
|
||||
*
|
||||
* @return reference to buffer for convenience
|
||||
*/
|
||||
protected abstract R obtainRecord(int index, R buffer);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package picante.data.list;
|
||||
|
||||
/**
|
||||
* An abstract class that implements both required search algorithms utilizing a simple binary
|
||||
* search with the {@link GaugedRetrievable#getGauge(int)} method.
|
||||
* <p>
|
||||
* If the efficiency of searches over an implementation of the interface is a concern, or if binary
|
||||
* search utilizing the {@link GaugedRetrievable#getGauge(int)} method is sufficient, then simply
|
||||
* subclass this abstract class and supply the standard retrieval methods.
|
||||
* </p>
|
||||
*
|
||||
* @param <R> the type of record required by the implementation
|
||||
*/
|
||||
public abstract class AbstractGaugedRetrievable<R> implements GaugedRetrievableLLET<R> {
|
||||
|
||||
private final Searcher searcher = new Searcher(this);
|
||||
|
||||
@Override
|
||||
public int indexLastLessThanOrEqualTo(double time) {
|
||||
return searcher.indexLastLessThanOrEqualTo(time);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int indexLastLessThan(double time) {
|
||||
return searcher.indexLastLessThan(time);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package picante.data.list;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkElementIndex;
|
||||
|
||||
/**
|
||||
* A layer of abstraction to manage the {@link IndexOutOfBoundsException} generated by retrieval
|
||||
* methods in the {@link GaugedRetrievable} interface.
|
||||
* <p>
|
||||
* This class will manage the generation of index out of bounds exceptions, blocking access to
|
||||
* invoking the {@link AbstractGaugedRetrievableWithExceptions#obtain(int, Object)} or
|
||||
* {@link AbstractGaugedRetrievableWithExceptions#obtainGauge(int)} methods if the supplied index
|
||||
* lies outside the range allowed by the {@link AbstractGaugedRetrievableWithExceptions#size()}
|
||||
* method. It is simply a convenience class, if you do not wish to manage the error handling in your
|
||||
* own code.
|
||||
* </p>
|
||||
*
|
||||
* @param <R> the type of record required by the implementation
|
||||
*/
|
||||
public abstract class AbstractGaugedRetrievableWithExceptions<R>
|
||||
extends AbstractGaugedRetrievable<R> {
|
||||
|
||||
@Override
|
||||
public R get(int index, R buffer) {
|
||||
checkElementIndex(index, size(), "Record index");
|
||||
return obtain(index, buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to actually retrieve the record associated with a particular index. This is used
|
||||
* directly in the implementation of {@link AbstractGaugedRetrievableWithExceptions#get(int)} to
|
||||
* retrieve the record.
|
||||
*
|
||||
* @param index the index of interest
|
||||
* @param buffer the mutable buffer to receive the resultant record
|
||||
*
|
||||
* @return a reference to buffer for convenience
|
||||
*/
|
||||
protected abstract R obtain(int index, R buffer);
|
||||
|
||||
@Override
|
||||
public double getGauge(int index) {
|
||||
checkElementIndex(index, size(), "Gauge index");
|
||||
return obtainGauge(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to actually retrieve the gauged value associated with a particular index. This is used
|
||||
* directly in the implementation of {@link AbstractGaugedIndexableWithExceptions#getGauge(int)}
|
||||
* to actually read the measure value associated with the record.
|
||||
*
|
||||
* @param index index of interest
|
||||
*
|
||||
* @return the double capturing the associated gauged value
|
||||
*/
|
||||
protected abstract double obtainGauge(int index);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package picante.data.list;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkElementIndex;
|
||||
|
||||
/**
|
||||
* A layer of abstraction to manage the {@link IndexOutOfBoundsException} generated by the retrieval
|
||||
* method in the {@link Retrievable} interface.
|
||||
* <p>
|
||||
* This class will manage the generation of index out of bounds exceptions, blocking access to
|
||||
* invoking the {@link AbstractRetrievableWithExceptions#obtain(int, Object)} method if the supplied
|
||||
* index lies outside the range allowed by the {@link AbstractRetrievableWithExceptions#size()}
|
||||
* method. It is simply a convenience class allowing a user to skip implementing the necessary error
|
||||
* handling code on their own.
|
||||
* </p>
|
||||
*
|
||||
* @param <R>
|
||||
*/
|
||||
public abstract class AbstractRetrievableWithExceptions<R> implements Retrievable<R> {
|
||||
|
||||
@Override
|
||||
public R get(int index, R buffer) {
|
||||
checkElementIndex(index, size());
|
||||
return obtain(index, buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to actually retrieve the record associated with a particular index. This is used
|
||||
* directly in the implementation of {@link AbstractGaugedRetrievableWithExceptions#get(int)} to
|
||||
* retrieve the record.
|
||||
*
|
||||
* @param index the index of interest
|
||||
* @param buffer the mutable buffer to receive the resultant record
|
||||
*
|
||||
* @return a reference to buffer for convenience
|
||||
*/
|
||||
protected abstract R obtain(int index, R buffer);
|
||||
|
||||
}
|
||||
25
src/main/java/picante/data/list/Gaugeable.java
Normal file
25
src/main/java/picante/data/list/Gaugeable.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package picante.data.list;
|
||||
|
||||
/**
|
||||
* Interface defining a gauge used to measure the distance between records.
|
||||
* <p>
|
||||
* Typically the gauge is measuring separation in time between records in a time series, but this is
|
||||
* entirely abstract in that it could be temperatures or some other measurable quantity. This
|
||||
* interface exists primarily to specify how the conversion from one object to the double gauge will
|
||||
* occur.
|
||||
* </p>
|
||||
*
|
||||
* @param <T> the object to convert to a gauge
|
||||
*/
|
||||
public interface Gaugeable<T> {
|
||||
|
||||
/**
|
||||
* Compute the position of the supplied value against this gauge.
|
||||
*
|
||||
* @param t the value to gauge
|
||||
*
|
||||
* @return the gauge associated with the value
|
||||
*/
|
||||
double gauge(T t);
|
||||
|
||||
}
|
||||
48
src/main/java/picante/data/list/Gaugeables.java
Normal file
48
src/main/java/picante/data/list/Gaugeables.java
Normal file
@@ -0,0 +1,48 @@
|
||||
package picante.data.list;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
|
||||
/**
|
||||
* Static utility methods pertaining to {@link Gaugeable}s.
|
||||
*
|
||||
*/
|
||||
public final class Gaugeables {
|
||||
|
||||
private Gaugeables() {}
|
||||
|
||||
/**
|
||||
* Adapts a standard Guava function to a {@link Gaugeable}.
|
||||
*
|
||||
* @param converter the function to adapt
|
||||
*
|
||||
* @return a newly created {@link Gaugeable} that just invokes the apply method on the supplied
|
||||
* function.
|
||||
*/
|
||||
public static <T> Gaugeable<T> from(final Function<T, Double> converter) {
|
||||
return new Gaugeable<T>() {
|
||||
|
||||
@Override
|
||||
public double gauge(T t) {
|
||||
return converter.apply(t);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The identity gauge is for an Indexable<Double> that is its own gauge. (Note that this method
|
||||
* deals only with type Double - its not generic!)
|
||||
*
|
||||
* @return a new {@link Gaugeable} that just returns the value
|
||||
*/
|
||||
public static Gaugeable<Double> identity() {
|
||||
return new Gaugeable<Double>() {
|
||||
@Override
|
||||
public double gauge(Double t) {
|
||||
return t;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
26
src/main/java/picante/data/list/Gauged.java
Normal file
26
src/main/java/picante/data/list/Gauged.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package picante.data.list;
|
||||
|
||||
/**
|
||||
* Simple expression of the gauged retrieval method.
|
||||
* <p>
|
||||
* This interface exists purely to allow functionality from the {@link Indexable} and
|
||||
* {@link Retrievable} inheritance trees to share code.
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
public interface Gauged {
|
||||
|
||||
/**
|
||||
* Retrieve the gauge (time) associated with a record at a particular index.
|
||||
*
|
||||
* @param index the index of interest
|
||||
*
|
||||
* @return the double capturing the measured value against which records are recorded, typically
|
||||
* time.
|
||||
*
|
||||
* @throws IndexOutOfBoundsException if the index lies outside the range of acceptable values
|
||||
* supporting the instance
|
||||
*/
|
||||
double getGauge(int index);
|
||||
|
||||
}
|
||||
13
src/main/java/picante/data/list/GaugedRetrievable.java
Normal file
13
src/main/java/picante/data/list/GaugedRetrievable.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package picante.data.list;
|
||||
|
||||
/**
|
||||
* Extension of the list interface that requires a companion method to obtain a gauged quantity
|
||||
* (typically time) associated with a particular record. The elements of the {@link Retrievable} are
|
||||
* assumed to be sorted in non-decreasing order against the gauge (and generally strictly
|
||||
* increasing).
|
||||
*
|
||||
* @param <R> the type of record required by the implementation
|
||||
*/
|
||||
public interface GaugedRetrievable<R> extends Retrievable<R>, Gauged {
|
||||
|
||||
}
|
||||
30
src/main/java/picante/data/list/GaugedRetrievableLLE.java
Normal file
30
src/main/java/picante/data/list/GaugedRetrievableLLE.java
Normal file
@@ -0,0 +1,30 @@
|
||||
package picante.data.list;
|
||||
|
||||
/**
|
||||
* Extension of the {@link GaugedRetrievable} interface that provides a method for performing last
|
||||
* less than or equal to searches on the list of gauged (typically temporally) non-decreasing
|
||||
* records.
|
||||
* <p>
|
||||
* The methods on the {@link GaugedRetrievable} interface, specifically:
|
||||
* {@link GaugedRetrievable#getGauge(int)} allow generic binary searching to be performed on the
|
||||
* contents of any sequence of records against the gauge. However, this particular interface leaves
|
||||
* it entirely up to the implementation as to how that search should be performed. It may be able to
|
||||
* exploit additional information not captured by the generic interface, but known to the
|
||||
* implementation to greatly improve performance.
|
||||
* </p>
|
||||
*
|
||||
* @param <R> the type of record required by the implementation
|
||||
*/
|
||||
public interface GaugedRetrievableLLE<R> extends GaugedRetrievable<R> {
|
||||
|
||||
/**
|
||||
* Obtain the index of the record that is last less than or equal to the supplied gauge value.
|
||||
*
|
||||
* @param gauge double containing the gauge value of interest
|
||||
*
|
||||
* @return an integer in the range [-1, {@link Retrievable#size()}-1]. -1 indicates the supplied
|
||||
* gauge occurs prior to the gauge values associated with all the records
|
||||
*/
|
||||
public int indexLastLessThanOrEqualTo(double gauge);
|
||||
|
||||
}
|
||||
11
src/main/java/picante/data/list/GaugedRetrievableLLET.java
Normal file
11
src/main/java/picante/data/list/GaugedRetrievableLLET.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package picante.data.list;
|
||||
|
||||
/**
|
||||
* Marker interface extension of the {@link GaugedRetrievable} interface that provides both strictly
|
||||
* last less than and last less than or equal to searches.
|
||||
*
|
||||
* @param <R> the type of record required by the implementation
|
||||
*/
|
||||
public interface GaugedRetrievableLLET<R> extends GaugedRetrievableLLE<R>, GaugedRetrievableLLT<R> {
|
||||
|
||||
}
|
||||
31
src/main/java/picante/data/list/GaugedRetrievableLLT.java
Normal file
31
src/main/java/picante/data/list/GaugedRetrievableLLT.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package picante.data.list;
|
||||
|
||||
|
||||
/**
|
||||
* Extension of the {@link GaugedRetrievable} interface that provides a method for performing
|
||||
* strictly last less than searches on the list of gauged (typically temporally) non-decreasing
|
||||
* records.
|
||||
* <p>
|
||||
* The methods on the {@link GaugedRetrievable} interface, specifically:
|
||||
* {@link GaugedRetrievable#getGauge(int)} allow generic binary searching to be performed on the
|
||||
* contents of any sequence of records against the gauge. However, this particular interface leaves
|
||||
* it entirely up to the implementation as to how that search should be performed. It may be able to
|
||||
* exploit additional information not captured by the generic interface, but known to the
|
||||
* implementation, to greatly improve performance.
|
||||
* </p>
|
||||
*
|
||||
* @param <R> the type of record required by the implementation
|
||||
*/
|
||||
public interface GaugedRetrievableLLT<R> extends GaugedRetrievable<R> {
|
||||
|
||||
/**
|
||||
* Obtain the index of the record that is strictly less than the supplied gauge value.
|
||||
*
|
||||
* @param gauge double containing the gauge value of interest
|
||||
*
|
||||
* @return an integer in the range [-1, {@link Retrievable#size()} -1]. -1 indicates the supplied
|
||||
* gauge value is equal to the first record's gauge or before all records
|
||||
*/
|
||||
public int indexLastLessThan(double gauge);
|
||||
|
||||
}
|
||||
32
src/main/java/picante/data/list/Retrievable.java
Normal file
32
src/main/java/picante/data/list/Retrievable.java
Normal file
@@ -0,0 +1,32 @@
|
||||
package picante.data.list;
|
||||
|
||||
/**
|
||||
* Basic interface for describing a list of indexed records that are retrieved with a mutable buffer
|
||||
* pattern.
|
||||
*
|
||||
* @param <R> the type of the record required by the implementation
|
||||
*/
|
||||
public interface Retrievable<R> {
|
||||
|
||||
/**
|
||||
* Retrieve a particular record from the list
|
||||
*
|
||||
* @param index the index of interest
|
||||
*
|
||||
* @param buffer the record buffer to receive the results
|
||||
*
|
||||
* @return a reference to buffer for convenience
|
||||
*
|
||||
* @throws IndexOutOfBoundsException if index lies outside the acceptable range supported by the
|
||||
* instance: [0, {@link Sizeable#size()}-1].
|
||||
*/
|
||||
R get(int index, R buffer);
|
||||
|
||||
/**
|
||||
* Get the size of the list
|
||||
*
|
||||
* @return the number of records in the list
|
||||
*/
|
||||
public int size();
|
||||
|
||||
}
|
||||
42
src/main/java/picante/data/list/Retrievables.java
Normal file
42
src/main/java/picante/data/list/Retrievables.java
Normal file
@@ -0,0 +1,42 @@
|
||||
package picante.data.list;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkElementIndex;
|
||||
import picante.designpatterns.Writable;
|
||||
|
||||
public class Retrievables {
|
||||
|
||||
public <R extends Writable<? super R, R>> Retrievable<R> of(final R value) {
|
||||
return new Retrievable<R>() {
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public R get(int index, R buffer) {
|
||||
checkElementIndex(index, 1);
|
||||
return buffer.setTo(value);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
public <S, R extends Writable<S, R>> Retrievable<R> of(@SuppressWarnings("unused") Class<R> clazz,
|
||||
final S value) {
|
||||
return new Retrievable<R>() {
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public R get(int index, R buffer) {
|
||||
checkElementIndex(index, 1);
|
||||
return buffer.setTo(value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
119
src/main/java/picante/data/list/Searcher.java
Normal file
119
src/main/java/picante/data/list/Searcher.java
Normal file
@@ -0,0 +1,119 @@
|
||||
package picante.data.list;
|
||||
|
||||
/**
|
||||
* Simple class that provides implementations of the two standard search algorithms supported by
|
||||
* this package using binary search over the {@link Gauged#getGauge(int)} method.
|
||||
* <p>
|
||||
* This class is package private, as there is no need for external users to access it. The abstract
|
||||
* classes provided in the package give indirect access to it, as do the static methods on various
|
||||
* utility classes.
|
||||
* </p>
|
||||
*/
|
||||
class Searcher {
|
||||
|
||||
private final GaugedRetrievable<?> list;
|
||||
|
||||
Searcher(GaugedRetrievable<?> list) {
|
||||
this.list = list;
|
||||
}
|
||||
|
||||
public int indexLastLessThanOrEqualTo(double time) {
|
||||
int items = list.size();
|
||||
int begin = 0;
|
||||
int end = items - 1;
|
||||
int middle;
|
||||
int j;
|
||||
|
||||
/*
|
||||
* Next handle the case where none of the elements in the array are less than the search value.
|
||||
*/
|
||||
double queryValue = list.getGauge(begin);
|
||||
|
||||
if (time < queryValue) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* And the case where all of the elements in the array are less than or equal to the search
|
||||
* value.
|
||||
*/
|
||||
queryValue = list.getGauge(end);
|
||||
|
||||
if (time >= queryValue) {
|
||||
return end;
|
||||
}
|
||||
|
||||
/*
|
||||
* The boundary cases have been handled, initiate the search over the segment contents.
|
||||
*/
|
||||
while (items > 2) {
|
||||
|
||||
j = items / 2;
|
||||
middle = begin + j;
|
||||
|
||||
queryValue = list.getGauge(middle);
|
||||
|
||||
if (queryValue <= time) {
|
||||
begin = middle;
|
||||
} else {
|
||||
end = middle;
|
||||
}
|
||||
|
||||
items = 1 + (end - begin);
|
||||
}
|
||||
|
||||
return begin;
|
||||
|
||||
}
|
||||
|
||||
public int indexLastLessThan(double time) {
|
||||
|
||||
int items = list.size();
|
||||
int begin = 0;
|
||||
int end = items - 1;
|
||||
int middle;
|
||||
int j;
|
||||
|
||||
/*
|
||||
* Next handle the case where none of the elements in the array are less than the search value.
|
||||
*/
|
||||
double queryValue = list.getGauge(begin);
|
||||
|
||||
if (time <= queryValue) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* And the case where all of the elements in the array are less than the search value.
|
||||
*/
|
||||
queryValue = list.getGauge(end);
|
||||
|
||||
if (queryValue < time) {
|
||||
return end;
|
||||
}
|
||||
|
||||
/*
|
||||
* The boundary cases have been handled, initiate the search over the segment contents.
|
||||
*/
|
||||
while (items > 2) {
|
||||
|
||||
j = items / 2;
|
||||
middle = begin + j;
|
||||
|
||||
queryValue = list.getGauge(middle);
|
||||
|
||||
if (queryValue < time) {
|
||||
begin = middle;
|
||||
} else {
|
||||
end = middle;
|
||||
}
|
||||
|
||||
items = 1 + (end - begin);
|
||||
|
||||
}
|
||||
|
||||
return begin;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
33
src/main/java/picante/data/list/package-info.java
Normal file
33
src/main/java/picante/data/list/package-info.java
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Package containing the generic crucible one dimensional, record based data model.
|
||||
* <p>
|
||||
* The primary "record" storage interface is:
|
||||
* <dl>
|
||||
* <dt>{@link Retrievable}</dt>
|
||||
* <dd>Interfaces that use an inversion of control memory management pattern, much like the
|
||||
* {@link picante.designpatterns.Writable} design pattern. The caller supplies the memory that
|
||||
* the implementation then mutates to capture the result.</dd>
|
||||
* </dl>
|
||||
* </p>
|
||||
* <p>
|
||||
* The next concept that is present in these packages is that of the "gauge". We struggled
|
||||
* to come up with unique terminology that captures the essence of what we are trying to express
|
||||
* here. The basic idea is that if you wish to interpolate between "records" then you
|
||||
* likely need these records registered against some sort of domain or number line. This
|
||||
* "domain" axis is the gauge, and it is an ordered association of records with a double
|
||||
* precision number. Thus the gauge locates records on the table directly to a position on this
|
||||
* domain number line or axis. Frequently the gauge is some measure of time, but the API makes no
|
||||
* such restriction. Implementations of these gauged interfaces are expected to provide the records
|
||||
* in an index order that is non-decreasing against the gauge.
|
||||
* </p>
|
||||
* <p>
|
||||
* Lastly, because at times if the implementations of these interfaces are backed by databases,
|
||||
* files, or other means, there are a set of additional extensions that add methods to perform
|
||||
* searches. Allowing implementors to provide search mechanisms that are most efficient for their
|
||||
* respective data stores or implementations seemed appropriate. In most cases, binary search or
|
||||
* direct index computation is sufficient. Abstract helper classes that provide commonly used
|
||||
* searching algorithms are available in an additional sub-package.
|
||||
* </p>
|
||||
*/
|
||||
package picante.data.list;
|
||||
|
||||
172
src/main/java/picante/demo/Geometry.java
Normal file
172
src/main/java/picante/demo/Geometry.java
Normal file
@@ -0,0 +1,172 @@
|
||||
package picante.demo;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import picante.math.coords.CoordConverters;
|
||||
import picante.math.coords.LatitudinalVector;
|
||||
import picante.math.vectorspace.UnwritableVectorIJK;
|
||||
import picante.math.vectorspace.VectorIJK;
|
||||
import picante.mechanics.CelestialBodies;
|
||||
import picante.mechanics.CelestialFrames;
|
||||
import picante.mechanics.Coverage;
|
||||
import picante.mechanics.EphemerisID;
|
||||
import picante.mechanics.FrameID;
|
||||
import picante.mechanics.FrameTransformFunction;
|
||||
import picante.mechanics.PositionVectorFunction;
|
||||
import picante.mechanics.providers.aberrated.AberratedEphemerisProvider;
|
||||
import picante.mechanics.providers.aberrated.AberrationCorrection;
|
||||
import picante.mechanics.utilities.SimpleEphemerisID;
|
||||
import picante.spice.MetakernelReader;
|
||||
import picante.spice.SpiceEnvironment;
|
||||
import picante.spice.SpiceEnvironmentBuilder;
|
||||
import picante.spice.adapters.AdapterInstantiationException;
|
||||
import picante.spice.fov.FOV;
|
||||
import picante.spice.fov.FOVFactory;
|
||||
import picante.spice.kernel.KernelInstantiationException;
|
||||
import picante.surfaces.Ellipsoid;
|
||||
import picante.surfaces.Surfaces;
|
||||
import picante.time.TimeConversion;
|
||||
import picante.utilities.SPICEUtils;
|
||||
|
||||
/**
|
||||
* Picante version of SPICE example in the NAIF tutorials (e.g. <a href=
|
||||
* "https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/Tutorials/pdf/individual_docs/38_program_idl.pdf">IDL</a>).
|
||||
* See slide 23 for results to compare.
|
||||
*
|
||||
* <p>This does not agree exactly with SPICE as the ellipsoid intersect calculations are done
|
||||
* differently.
|
||||
*
|
||||
* @author Hari.Nair@jhuapl.edu
|
||||
*/
|
||||
public class Geometry {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
EphemerisID bodyID = CelestialBodies.PHOEBE;
|
||||
FrameID frameID = CelestialFrames.IAU_PHOEBE;
|
||||
EphemerisID spacecraftID = new SimpleEphemerisID("CASSINI");
|
||||
String instName = "CASSINI_ISS_NAC";
|
||||
String utcString = "2004 jun 11 19:32:00";
|
||||
|
||||
// this is necessary to tell the SpiceEnvironment about objects that are not in the
|
||||
// CelestialBodies enumeration
|
||||
Map<String, EphemerisID> bindings = new HashMap<>();
|
||||
bindings.put("CASSINI", spacecraftID);
|
||||
|
||||
// The MetakernelReader creates the list of files to be read - you may need to run the scripts
|
||||
// src/test/resources/kernels/spk/getSPK.bash
|
||||
// src/test/resources/kernels/ck/getCK.bash
|
||||
// to download all the necessary kernels
|
||||
MetakernelReader reader = new MetakernelReader("src/test/resources/kernels/mk/geometry.tm");
|
||||
|
||||
// build the SpiceEnvironment from the list of kernels and object bindings
|
||||
SpiceEnvironment env = null;
|
||||
try {
|
||||
SpiceEnvironmentBuilder builder = new SpiceEnvironmentBuilder();
|
||||
for (String name : bindings.keySet()) builder.bindEphemerisID(name, bindings.get(name));
|
||||
for (File kernel : reader.getKernelsToLoad()) builder.load(kernel.getName(), kernel);
|
||||
env = builder.build();
|
||||
} catch (KernelInstantiationException | IOException e) {
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
// convert the utcString to a TDB time
|
||||
TimeConversion tc = new TimeConversion(env.getLSK());
|
||||
double tdb = tc.utcStringToTDB(utcString);
|
||||
|
||||
// create an ephemeris and frame provider from the SpiceEnvironment
|
||||
AberratedEphemerisProvider provider = null;
|
||||
try {
|
||||
provider = env.createTripleAberratedProvider();
|
||||
} catch (AdapterInstantiationException e) {
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
// this function evaluates to the vector from the target (Phoebe) to the spacecraft (CASSINI)
|
||||
// in Phoebe's body fixed frame
|
||||
PositionVectorFunction targetToSc =
|
||||
provider.createAberratedPositionVectorFunction(
|
||||
spacecraftID, bodyID, frameID, Coverage.ALL_TIME, AberrationCorrection.LT_S);
|
||||
|
||||
// This function evaluates to the vector from the target (Phoebe) to the Sun
|
||||
// in Phoebe's body fixed frame
|
||||
PositionVectorFunction targetToSun =
|
||||
provider.createAberratedPositionVectorFunction(
|
||||
CelestialBodies.SUN, bodyID, frameID, Coverage.ALL_TIME, AberrationCorrection.LT_S);
|
||||
|
||||
// Get Phoebe's shape from the kernel pool
|
||||
ImmutableList<Double> radii = env.getBodyRadii().get(bodyID);
|
||||
Ellipsoid shape = Surfaces.createEllipsoidalSurface(radii.get(0), radii.get(1), radii.get(2));
|
||||
|
||||
// This is the vector from Phoebe's center to CASSINI
|
||||
UnwritableVectorIJK scPosBodyFixed = targetToSc.getPosition(tdb);
|
||||
|
||||
// check that the instrument name is in the kernel pool
|
||||
Map<String, Integer> bodn2c = SPICEUtils.BODN2C(env);
|
||||
if (!bodn2c.containsKey(instName))
|
||||
throw new RuntimeException("No body with name " + instName + " found in kernel pool.");
|
||||
|
||||
int instID = bodn2c.get(instName);
|
||||
|
||||
// build the FOV object for the camera
|
||||
FOVFactory fovFactory = new FOVFactory(env.getPool());
|
||||
FOV fov = fovFactory.create(instID);
|
||||
|
||||
// this is the camera boresight in the instrument frame
|
||||
UnwritableVectorIJK boresight = fov.getFovSpice().getBoresight();
|
||||
|
||||
// transform a vector in the instrument frame to Phoebe's body fixed frame
|
||||
FrameID instrFrame = fov.getFovSpice().getFrame();
|
||||
FrameTransformFunction instrToBody =
|
||||
provider.createFrameTransformFunction(instrFrame, frameID, Coverage.ALL_TIME);
|
||||
|
||||
// This is the boresight in Phoebe's body fixed frame
|
||||
UnwritableVectorIJK boresightBodyFixed = instrToBody.getTransform(tdb).mxv(boresight);
|
||||
|
||||
// find the intersection point. The shape, spacecraft position, and look direction are all in IAU_PHOEBE
|
||||
if (!shape.intersects(scPosBodyFixed, boresightBodyFixed)) {
|
||||
System.out.println("No intersection found.");
|
||||
System.exit(0);
|
||||
}
|
||||
VectorIJK sincpt = shape.compute(scPosBodyFixed, boresightBodyFixed, new VectorIJK());
|
||||
|
||||
// find the illumination angles
|
||||
VectorIJK normal = shape.computeOutwardNormal(sincpt);
|
||||
VectorIJK pointToSc = VectorIJK.subtract(scPosBodyFixed, sincpt);
|
||||
|
||||
UnwritableVectorIJK sunPosBodyFixed = targetToSun.getPosition(tdb);
|
||||
VectorIJK pointToSun = VectorIJK.subtract(sunPosBodyFixed, sincpt);
|
||||
|
||||
double phase = pointToSun.getSeparation(pointToSc);
|
||||
double incidence = pointToSun.getSeparation(normal);
|
||||
double emission = pointToSc.getSeparation(normal);
|
||||
|
||||
// planetocentric coordinates
|
||||
LatitudinalVector pc = CoordConverters.convertToLatitudinal(sincpt);
|
||||
|
||||
// planetographic coordinates
|
||||
LatitudinalVector pg = SPICEUtils.RECGEO(shape, sincpt);
|
||||
|
||||
System.out.printf(
|
||||
"Date (UTC): %s\n", tc.tdbToUTCString(tdb, "ISOC"));
|
||||
System.out.printf(
|
||||
"Intercept planetocentric longitude (deg): %f\n", Math.toDegrees(pc.getLongitude()));
|
||||
System.out.printf(
|
||||
"Intercept planetocentric latitude (deg): %f\n", Math.toDegrees(pc.getLatitude()));
|
||||
System.out.printf(
|
||||
"Intercept planetodetic longitude (deg): %f\n", Math.toDegrees(pg.getLongitude()));
|
||||
System.out.printf(
|
||||
"Intercept planetodetic latitude (deg): %f\n", Math.toDegrees(pg.getLatitude()));
|
||||
System.out.printf("Range from spacecraft to intercept point (km): %f\n", pointToSc.getLength());
|
||||
System.out.printf("Intercept phase angle (deg): %f\n", Math.toDegrees(phase));
|
||||
System.out.printf(
|
||||
"Intercept solar incidence angle (deg): %f\n", Math.toDegrees(incidence));
|
||||
System.out.printf(
|
||||
"Intercept emission angle (deg): %f\n", Math.toDegrees(emission));
|
||||
}
|
||||
}
|
||||
196
src/main/java/picante/demo/MultiThreaded.java
Normal file
196
src/main/java/picante/demo/MultiThreaded.java
Normal file
@@ -0,0 +1,196 @@
|
||||
package picante.demo;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import picante.math.vectorspace.VectorIJK;
|
||||
import picante.mechanics.CelestialBodies;
|
||||
import picante.mechanics.CelestialFrames;
|
||||
import picante.mechanics.Coverage;
|
||||
import picante.mechanics.EphemerisID;
|
||||
import picante.mechanics.FrameID;
|
||||
import picante.mechanics.StateVector;
|
||||
import picante.mechanics.StateVectorFunction;
|
||||
import picante.mechanics.providers.aberrated.AberratedEphemerisProvider;
|
||||
import picante.mechanics.providers.aberrated.AberrationCorrection;
|
||||
import picante.spice.MetakernelReader;
|
||||
import picante.spice.SpiceEnvironment;
|
||||
import picante.spice.SpiceEnvironmentBuilder;
|
||||
import picante.spice.kernel.KernelInstantiationException;
|
||||
import picante.time.TimeConversion;
|
||||
|
||||
/**
|
||||
* Example for setting up a multithreaded spice calculation.
|
||||
*
|
||||
* @author Hari.Nair@jhuapl.edu
|
||||
*/
|
||||
public class MultiThreaded implements Callable<Map<Double, StateVector>> {
|
||||
|
||||
private final String metakernel;
|
||||
private final EphemerisID target;
|
||||
private final EphemerisID observer;
|
||||
private final FrameID frameID;
|
||||
private final int numPts;
|
||||
private final double startTime;
|
||||
private final double delta;
|
||||
private final ThreadLocal<SpiceEnvironment> spiceEnv;
|
||||
|
||||
public MultiThreaded(
|
||||
String metakernel,
|
||||
EphemerisID target,
|
||||
EphemerisID observer,
|
||||
FrameID frameID,
|
||||
int numPts,
|
||||
double startTime,
|
||||
double delta) {
|
||||
this.metakernel = metakernel;
|
||||
this.target = target;
|
||||
this.observer = observer;
|
||||
this.frameID = frameID;
|
||||
this.numPts = numPts;
|
||||
this.startTime = startTime;
|
||||
this.delta = delta;
|
||||
this.spiceEnv = new ThreadLocal<>();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
EphemerisID target = CelestialBodies.MARS;
|
||||
EphemerisID observer = CelestialBodies.EARTH;
|
||||
FrameID frameID = CelestialFrames.J2000;
|
||||
String epoch = "2003 Jul 4 19:00:00";
|
||||
|
||||
String metakernel = "src/test/resources/kernels/mk/multiThreaded.tm";
|
||||
SpiceEnvironment env = getSpiceEnvironment(metakernel);
|
||||
|
||||
TimeConversion tc = new TimeConversion(env.getLSK());
|
||||
double startTime = tc.utcStringToTDB(epoch);
|
||||
|
||||
int numPts = 2000000;
|
||||
double delta = 1.5;
|
||||
|
||||
// single thread
|
||||
MultiThreaded app =
|
||||
new MultiThreaded(metakernel, target, observer, frameID, numPts, startTime, delta);
|
||||
|
||||
long startMillis = System.currentTimeMillis();
|
||||
Map<Double, StateVector> oneThread = new HashMap<>();
|
||||
try {
|
||||
oneThread = app.call();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
System.out.printf(
|
||||
"Single thread time: %d entries in %.3f seconds\n",
|
||||
oneThread.size(), 1e-3 * (System.currentTimeMillis() - startMillis));
|
||||
|
||||
startMillis = System.currentTimeMillis();
|
||||
|
||||
int numThreads = 5;
|
||||
ExecutorService executor = Executors.newFixedThreadPool(numThreads);
|
||||
Set<Future<Map<Double, StateVector>>> futures = new HashSet<>();
|
||||
|
||||
for (int i = 0; i < numThreads; i++) {
|
||||
int thisNumPts = numPts / numThreads;
|
||||
double thisStartTime = startTime + i * thisNumPts * delta;
|
||||
app =
|
||||
new MultiThreaded(
|
||||
metakernel, target, observer, frameID, thisNumPts, thisStartTime, delta);
|
||||
futures.add(executor.submit(app));
|
||||
}
|
||||
|
||||
Map<Double, StateVector> allThreads = new HashMap<>();
|
||||
for (Future<Map<Double, StateVector>> future : futures) {
|
||||
try {
|
||||
allThreads.putAll(future.get());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
executor.shutdown();
|
||||
|
||||
System.out.printf(
|
||||
"%d thread time: %d entries in %.3f seconds\n",
|
||||
numThreads, allThreads.size(), 1e-3 * (System.currentTimeMillis() - startMillis));
|
||||
|
||||
System.out.print("Comparing multiple thread calculations with single thread... ");
|
||||
double tolerance = 0;
|
||||
for (double key : oneThread.keySet()) {
|
||||
StateVector difference = StateVector.subtract(oneThread.get(key), allThreads.get(key));
|
||||
VectorIJK pos = difference.getPosition();
|
||||
VectorIJK vel = difference.getVelocity();
|
||||
|
||||
assertFalse(pos.getLength() > tolerance);
|
||||
assertFalse(vel.getLength() > tolerance);
|
||||
|
||||
if (pos.getLength() > tolerance || vel.getLength() > tolerance) {
|
||||
System.out.printf(
|
||||
"%s: states %s %s\n",
|
||||
tc.tdbToUTCString(key, "C"), oneThread.get(key), allThreads.get(key));
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("Finished");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param metakernel metakernel to read
|
||||
* @return SpiceEnvironment
|
||||
*/
|
||||
private static SpiceEnvironment getSpiceEnvironment(String metakernel) {
|
||||
MetakernelReader reader = new MetakernelReader(metakernel);
|
||||
SpiceEnvironment env = null;
|
||||
try {
|
||||
SpiceEnvironmentBuilder builder = new SpiceEnvironmentBuilder();
|
||||
for (File kernel : reader.getKernelsToLoad()) builder.load(kernel.getName(), kernel);
|
||||
env = builder.build();
|
||||
} catch (KernelInstantiationException | IOException e) {
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
return env;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A SpiceEnvironment unique to this thread
|
||||
*/
|
||||
private SpiceEnvironment getSpiceEnvironment() {
|
||||
if (spiceEnv.get() == null) {
|
||||
spiceEnv.set(getSpiceEnvironment(metakernel));
|
||||
}
|
||||
return spiceEnv.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Double, StateVector> call() throws Exception {
|
||||
|
||||
// create a new SpiceEnvironment only used by this thread
|
||||
SpiceEnvironment env = getSpiceEnvironment();
|
||||
|
||||
AberratedEphemerisProvider provider = env.createSingleAberratedProvider();
|
||||
StateVectorFunction stateVectorFunction =
|
||||
provider.createAberratedStateVectorFunction(
|
||||
target, observer, frameID, Coverage.ALL_TIME, AberrationCorrection.LT_S);
|
||||
|
||||
Map<Double, StateVector> map = new HashMap<>();
|
||||
for (int i = 0; i < numPts; i++) {
|
||||
double tdb = startTime + i * delta;
|
||||
StateVector state = stateVectorFunction.getState(tdb);
|
||||
map.put(tdb, state);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
}
|
||||
20
src/main/java/picante/designpatterns/Blueprint.java
Normal file
20
src/main/java/picante/designpatterns/Blueprint.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package picante.designpatterns;
|
||||
|
||||
/**
|
||||
* Interface describing a blueprint for a configuration of a builder. If you need code that applies
|
||||
* a configuration to a particular type of builder, then this interface should be utilized.
|
||||
*
|
||||
* @param <B> the type of builder for which this blueprint applies
|
||||
*/
|
||||
public interface Blueprint<B extends Builder<?, ?>> {
|
||||
|
||||
/**
|
||||
* Applies the blueprint to the builder.
|
||||
*
|
||||
* @param builder the builder to have the configuration applied
|
||||
*
|
||||
* @return a reference to the supplied builder for convenience
|
||||
*/
|
||||
public B configure(B builder);
|
||||
|
||||
}
|
||||
109
src/main/java/picante/designpatterns/Blueprints.java
Normal file
109
src/main/java/picante/designpatterns/Blueprints.java
Normal file
@@ -0,0 +1,109 @@
|
||||
package picante.designpatterns;
|
||||
|
||||
|
||||
/**
|
||||
* Utility methods for creating and manipulating {@link Blueprint} instances.
|
||||
*
|
||||
*/
|
||||
public class Blueprints {
|
||||
|
||||
/**
|
||||
* Static utility method collection need not be instantiated.
|
||||
*/
|
||||
private Blueprints() {}
|
||||
|
||||
/**
|
||||
* Static final field that holds the empty blueprint implementation.
|
||||
*/
|
||||
private static final Blueprint<?> EMPTY = new Blueprint<Builder<?, ?>>() {
|
||||
|
||||
@Override
|
||||
public Builder<?, ?> configure(Builder<?, ?> builder) {
|
||||
return builder;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a blueprint implementation that does nothing other than return a builder that it is
|
||||
* supplied.
|
||||
*
|
||||
* @return a "null" configuration builder that applies no configuration
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <B extends Builder<?, ?>> Blueprint<B> empty() {
|
||||
return (Blueprint<B>) EMPTY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new blueprint that concatenates the two supplied blueprints into one.
|
||||
* <p>
|
||||
* The resultant blueprint is effectively the same as:
|
||||
* <code>second.configure(first.configure(builder);</code>
|
||||
* </p>
|
||||
*
|
||||
* @param first the first blueprint to apply
|
||||
* @param second the second blueprint to apply
|
||||
*
|
||||
* @return a newly created blueprint that applies the first, then second to a builder.
|
||||
*/
|
||||
public static <B extends Builder<?, ?>> Blueprint<B> concat(final Blueprint<B> first,
|
||||
final Blueprint<B> second) {
|
||||
return new Blueprint<B>() {
|
||||
@Override
|
||||
public B configure(B builder) {
|
||||
return second.configure(first.configure(builder));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new blueprint that concatenates the supplied blueprints.
|
||||
* <p>
|
||||
* The resultant blueprint is effectively the same as invoking first on the builder, then each of
|
||||
* the elements in the order they are listed on the argument list from left to right.
|
||||
* </p>
|
||||
*
|
||||
* @param first the first blueprint to apply
|
||||
* @param blueprints the varargs list of blueprints to apply in order from left to right
|
||||
*
|
||||
* @return a newly created blueprint that applies the first, then subsequent to a builder
|
||||
*/
|
||||
@SafeVarargs
|
||||
public static <B extends Builder<?, ?>> Blueprint<B> concat(final Blueprint<B> first,
|
||||
final Blueprint<B>... blueprints) {
|
||||
return new Blueprint<B>() {
|
||||
|
||||
@Override
|
||||
public B configure(B builder) {
|
||||
B localBuilder = first.configure(builder);
|
||||
for (Blueprint<B> blueprint : blueprints) {
|
||||
localBuilder = blueprint.configure(localBuilder);
|
||||
}
|
||||
return localBuilder;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new blueprint that concatenates the supplied blueprints.
|
||||
*
|
||||
* @param blueprints the iterable of blueprints to concatenate.
|
||||
*
|
||||
* @return a newly created blueprint that applies supplied blueprints.
|
||||
*/
|
||||
public static <B extends Builder<?, ?>> Blueprint<B> concat(
|
||||
final Iterable<? extends Blueprint<B>> blueprints) {
|
||||
return new Blueprint<B>() {
|
||||
|
||||
@Override
|
||||
public B configure(B builder) {
|
||||
|
||||
for (Blueprint<B> blueprint : blueprints) {
|
||||
blueprint.configure(builder);
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package picante.designpatterns;
|
||||
|
||||
import picante.exceptions.PicanteRuntimeException;
|
||||
|
||||
/**
|
||||
* Runtime exception indicating a builder's build() method has failed.
|
||||
* <p>
|
||||
* While not an explicit requirement, users of the {@link Builder} pattern are encouraged to use
|
||||
* this runtime exception (or descendants of it) to indicate that a build has failed.
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
public class BuildFailedException extends PicanteRuntimeException {
|
||||
|
||||
/**
|
||||
* Default serial version UID.
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public BuildFailedException() {}
|
||||
|
||||
public BuildFailedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public BuildFailedException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public BuildFailedException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
41
src/main/java/picante/designpatterns/Builder.java
Normal file
41
src/main/java/picante/designpatterns/Builder.java
Normal file
@@ -0,0 +1,41 @@
|
||||
package picante.designpatterns;
|
||||
|
||||
/**
|
||||
* Simple interface defining the builder pattern.
|
||||
* <p>
|
||||
* A builder is different from a factory, in that a factory generally provides methods that are used
|
||||
* to immediately create an object of interest. The builder provides an API that is used to
|
||||
* configure the construction of a complex object. Effectively it provides a means to grow the
|
||||
* construction API independently of the object or interface to be constructed.
|
||||
* </p>
|
||||
* <p>
|
||||
* While this is not enforced via the interface mechanism, methods designed to configure the builder
|
||||
* on a specific implementation of a builder should always return references to the instance. This
|
||||
* allows method call chaining, which is typical of the builder pattern. Further, it is useful to
|
||||
* name the methods on the builder in a manner that lets them be read using natural language, i.e.
|
||||
* withOption(Option option), etc.
|
||||
* </p>
|
||||
* <p>
|
||||
* This interface would not technically have to exist to support the {@link Blueprint} interface,
|
||||
* but we felt it was useful in that all builders should have the build method on them.
|
||||
* </p>
|
||||
*
|
||||
* @param <T> the type of object the builder creates
|
||||
* @param <E> the type of exception the builder throws
|
||||
*
|
||||
* @see Blueprint
|
||||
*/
|
||||
public interface Builder<T, E extends Exception> {
|
||||
|
||||
/**
|
||||
* Builds the object or interface of interest.
|
||||
*
|
||||
* @return a newly created implementation of the desired interface or object
|
||||
*
|
||||
* @throws BuildFailedException implementors of this interface are encouraged to utilize this
|
||||
* runtime exception or a descendant of it to indicate that a build has failed for any
|
||||
* reason. It is, however; not explicitly required.
|
||||
*/
|
||||
public T build() throws E;
|
||||
|
||||
}
|
||||
60
src/main/java/picante/designpatterns/Writable.java
Normal file
60
src/main/java/picante/designpatterns/Writable.java
Normal file
@@ -0,0 +1,60 @@
|
||||
package picante.designpatterns;
|
||||
|
||||
/**
|
||||
* A interface which captures the Weak-Immutability pattern. Referred in Crucible as Writable and
|
||||
* Unwritable. The Unwritable version of the class has fields which cannot be set (protected
|
||||
* fields). Thus, it is Unwritable. The Writable version extends the Unwritable and provides setters
|
||||
* to the fields, with a basic setter defined by this interface. The implementor of the writable
|
||||
* class may and probably should write additional setters.
|
||||
* <p>
|
||||
* Note: An inherit shortcoming of this interface was noticed. If you attempt to write a class of
|
||||
* the following templated form:
|
||||
*
|
||||
* <pre>
|
||||
* class Something<U, W extends Writable<U, W>>
|
||||
* </pre>
|
||||
*
|
||||
* is not allowed by the compiler. This functionality seems to come up quite a bit when you are
|
||||
* trying to make generic classes that need to know simultaneously use the U and W versions of some
|
||||
* other class.
|
||||
* </p>
|
||||
* <p>
|
||||
* Another useful feature to add when creating classes that implement this pattern is the static
|
||||
* copyOf method on the unwritable class. It should look something like this:
|
||||
*
|
||||
* <pre>
|
||||
* public static UnwritableClass copyOf(UnwritableClass instance) {
|
||||
* if ( instance.getClass().equals(UnwritableClass.class) {
|
||||
* return instance;
|
||||
* }
|
||||
* return new UnwritableClass(instance); // the copy constructor
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* </p>
|
||||
*
|
||||
* @author G.K.Stephens
|
||||
*
|
||||
* @param <U> The unwritable version of the class that will be extending this interface.
|
||||
* @param <W> The class (writable) that will be extending this interface.
|
||||
*/
|
||||
public interface Writable<U, W> {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param u
|
||||
* @return a reference to the instance for convenience
|
||||
*/
|
||||
W setTo(U u);
|
||||
|
||||
/**
|
||||
*
|
||||
* @author stephgk1
|
||||
*
|
||||
* @param <U>
|
||||
* @param <W>
|
||||
*/
|
||||
public interface ImplementationInterface<U, W extends U> extends Writable<U, W> {
|
||||
}
|
||||
|
||||
}
|
||||
10
src/main/java/picante/designpatterns/package-info.java
Normal file
10
src/main/java/picante/designpatterns/package-info.java
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Package containing interfaces, and possibly framework classes used in the codification of design
|
||||
* patterns used throughout the crucible library.
|
||||
* <p>
|
||||
* Code is only really placed in this package when it is useful in a program, and not just for the
|
||||
* sake of codifying a concept.
|
||||
* </p>
|
||||
*/
|
||||
package picante.designpatterns;
|
||||
|
||||
33
src/main/java/picante/exceptions/BugException.java
Normal file
33
src/main/java/picante/exceptions/BugException.java
Normal file
@@ -0,0 +1,33 @@
|
||||
package picante.exceptions;
|
||||
|
||||
/**
|
||||
* Runtime exception generated as a result of something failing that should never fail.
|
||||
* <p>
|
||||
* Prototypical example would be invoking the string constructor: new String(new byte[] {32, 32},
|
||||
* "ISO-8859-1") the constructor may throw an exception due to an unsupported character encoding,
|
||||
* but this should never happen in practice.
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
public class BugException extends PicanteRuntimeException {
|
||||
|
||||
/**
|
||||
* Default serial version UID.
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public BugException() {}
|
||||
|
||||
public BugException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public BugException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public BugException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
17
src/main/java/picante/exceptions/ExceptionFactory.java
Normal file
17
src/main/java/picante/exceptions/ExceptionFactory.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package picante.exceptions;
|
||||
|
||||
/**
|
||||
* Interface describing an exception factory, designed to convert one type of exceptions to another
|
||||
* for abdicating control of exception generation to the user or implementor of an interface.
|
||||
* <p>
|
||||
* TODO: Provide a simple example.
|
||||
* </p>
|
||||
*
|
||||
* @param <I> Input exception type
|
||||
* @param <O> Output exception type
|
||||
*/
|
||||
public interface ExceptionFactory<O extends Throwable, I extends Throwable> {
|
||||
|
||||
public O create(I exception);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package picante.exceptions;
|
||||
|
||||
import picante.math.vectorspace.UnwritableVectorIJ;
|
||||
import picante.math.vectorspace.UnwritableVectorIJK;
|
||||
|
||||
/**
|
||||
* Runtime exception to be thrown when a function cannot be evaluated.
|
||||
*
|
||||
* @author G.K.Stephens
|
||||
*
|
||||
*/
|
||||
public class FunctionEvaluationException extends PicanteRuntimeException {
|
||||
|
||||
/**
|
||||
* Default serial version UID.
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Constructs a new runtime exception. The cause is not initialized, and may subsequently be
|
||||
* initialized by a call to {@link #initCause}.
|
||||
*
|
||||
*/
|
||||
public FunctionEvaluationException() {
|
||||
super(String.format("The function is not able to evaluate at the supplied value"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new runtime exception. The cause is not initialized, and may subsequently be
|
||||
* initialized by a call to {@link #initCause}.
|
||||
*
|
||||
* @param x the value that caused this exception to be generated
|
||||
*/
|
||||
public FunctionEvaluationException(double x) {
|
||||
super(String.format("The function is not able to evaluate at the value %s", x));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new runtime exception with the specified detail message. The cause is not
|
||||
* initialized, and may subsequently be initialized by a call to {@link #initCause}.
|
||||
*
|
||||
* @param x the value that caused this exception to be generated
|
||||
* @param message the detail message. The detail message is saved for later retrieval by the
|
||||
* {@link #getMessage()} method
|
||||
*/
|
||||
public FunctionEvaluationException(double x, String message) {
|
||||
super(String.format("The function is not able to evaluate at the value %s. %s", x, message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new runtime exception with the specified detail message. The cause is not
|
||||
* initialized, and may be subsequently initialized with a call to {@link #initCause(Throwable)}.
|
||||
*
|
||||
* @param x the value that caused this exception to be generated
|
||||
*
|
||||
* @param message the detail message.
|
||||
*
|
||||
*/
|
||||
public FunctionEvaluationException(UnwritableVectorIJ x, String message) {
|
||||
super(String.format("The function is not able to evaluate at the value %s. %s", x, message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new runtime exception with the specified detail message. The cause is not
|
||||
* initialized, and may be subsequently initialized with a call to {@link #initCause(Throwable)}.
|
||||
*
|
||||
* @param x the value that caused this exception to be generated
|
||||
*
|
||||
* @param message the detail message.
|
||||
*
|
||||
*/
|
||||
public FunctionEvaluationException(UnwritableVectorIJK x, String message) {
|
||||
super(String.format("The function is not able to evaluate at the value %s. %s", x, message));
|
||||
}
|
||||
|
||||
}
|
||||
27
src/main/java/picante/exceptions/PicanteException.java
Normal file
27
src/main/java/picante/exceptions/PicanteException.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package picante.exceptions;
|
||||
|
||||
/**
|
||||
* Parent of all exceptions generated by the picante library.
|
||||
*/
|
||||
public class PicanteException extends Exception {
|
||||
|
||||
/**
|
||||
* Default serial version UID.
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public PicanteException() {}
|
||||
|
||||
public PicanteException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public PicanteException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public PicanteException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package picante.exceptions;
|
||||
|
||||
/**
|
||||
* Parent of all runtime exceptions generated by the picante library.
|
||||
*/
|
||||
public class PicanteRuntimeException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Default serial version UID.
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public PicanteRuntimeException() {}
|
||||
|
||||
public PicanteRuntimeException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public PicanteRuntimeException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public PicanteRuntimeException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package picante.exceptions;
|
||||
|
||||
/**
|
||||
* A {@link RuntimeException} intended to perform the same function as {@link InterruptedException}
|
||||
* in cases where the API can not declare the checked variant provided by the JDK.
|
||||
*
|
||||
*/
|
||||
public class RuntimeInterruptedException extends PicanteRuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Default exception with no detail message
|
||||
*/
|
||||
public RuntimeInterruptedException() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception with detail message
|
||||
*
|
||||
* @param message tbe details
|
||||
*/
|
||||
public RuntimeInterruptedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
16
src/main/java/picante/exceptions/package-info.java
Normal file
16
src/main/java/picante/exceptions/package-info.java
Normal file
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Package containing general exceptions presented by the crucible library.
|
||||
* <p>
|
||||
* This package contains the parent classes of exception classes created in support of the crucible
|
||||
* library. In general, exceptions contained within the library should descend from either
|
||||
* <code>CrucibleException</code> or <code>CrucibleRuntimeException</code>.
|
||||
* </p>
|
||||
* <p>
|
||||
* This will allow developers utilizing the library the ability to trap exceptions specific to
|
||||
* crucible itself without much difficulty.
|
||||
* </p>
|
||||
*
|
||||
* @see picante.exceptions.PicanteException
|
||||
* @see picante.exceptions.PicanteRuntimeException
|
||||
*/
|
||||
package picante.exceptions;
|
||||
359
src/main/java/picante/junit/AssertTools.java
Normal file
359
src/main/java/picante/junit/AssertTools.java
Normal file
@@ -0,0 +1,359 @@
|
||||
package picante.junit;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.awt.geom.Point2D;
|
||||
import picante.math.intervals.UnwritableInterval;
|
||||
import picante.math.vectorspace.RotationMatrixIJK;
|
||||
import picante.math.vectorspace.UnwritableMatrixIJ;
|
||||
import picante.math.vectorspace.UnwritableMatrixIJK;
|
||||
import picante.math.vectorspace.UnwritableRotationMatrixIJK;
|
||||
import picante.math.vectorspace.UnwritableVectorIJ;
|
||||
import picante.math.vectorspace.UnwritableVectorIJK;
|
||||
import picante.mechanics.UnwritableStateTransform;
|
||||
import picante.mechanics.UnwritableStateVector;
|
||||
import picante.mechanics.rotations.AxisAndAngle;
|
||||
|
||||
public class AssertTools {
|
||||
|
||||
/**
|
||||
* Assert two doubles are exactly equal.
|
||||
*
|
||||
* @param expected Expected double value.
|
||||
* @param actual Actual double value.
|
||||
*/
|
||||
public static void assertEqualDouble(double expected, double actual) {
|
||||
assertEquals(expected, actual, 0.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert two doubles are equivalent.
|
||||
*
|
||||
* @param expected Expected double value.
|
||||
* @param actual Actual double value.
|
||||
*/
|
||||
public static void assertEquivalentDouble(double expected, double actual) {
|
||||
assertEquals(expected, actual, Math.ulp(expected));
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert two doubles are within the appropriate relative tolerance.
|
||||
*
|
||||
* @param message Failure message to emit
|
||||
* @param expected Expected double value.
|
||||
* @param actual Actual double value.
|
||||
* @param delta Required relative tolerance.
|
||||
*/
|
||||
public static void assertRelativeEquality(String message, double expected, double actual,
|
||||
double delta) {
|
||||
if (expected != actual) {
|
||||
assertEquals(message, expected, actual,
|
||||
delta * Math.max(Math.abs(expected), Math.abs(actual)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Assert two doubles are within the appropriate relative tolerance.
|
||||
*
|
||||
* @param expected Expected double value.
|
||||
* @param actual Actual double value.
|
||||
* @param delta Required relative tolerance.
|
||||
*/
|
||||
public static void assertRelativeEquality(double expected, double actual, double delta) {
|
||||
if (expected != actual) {
|
||||
assertEquals(expected, actual, delta * Math.max(Math.abs(expected), Math.abs(actual)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert two matrices are equivalent.
|
||||
*
|
||||
* @param expected Expected matrix.
|
||||
* @param actual Actual matrix.
|
||||
*/
|
||||
public static void assertEquivalentMatrix(UnwritableMatrixIJK expected,
|
||||
UnwritableMatrixIJK actual) {
|
||||
assertEquivalentDouble(expected.getII(), actual.getII());
|
||||
assertEquivalentDouble(expected.getIJ(), actual.getIJ());
|
||||
assertEquivalentDouble(expected.getIK(), actual.getIK());
|
||||
assertEquivalentDouble(expected.getJI(), actual.getJI());
|
||||
assertEquivalentDouble(expected.getJJ(), actual.getJJ());
|
||||
assertEquivalentDouble(expected.getJK(), actual.getJK());
|
||||
assertEquivalentDouble(expected.getKI(), actual.getKI());
|
||||
assertEquivalentDouble(expected.getKJ(), actual.getKJ());
|
||||
assertEquivalentDouble(expected.getKK(), actual.getKK());
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert two matrices are exactly equal.
|
||||
*
|
||||
* @param expected Expected matrix.
|
||||
* @param actual Actual matrix.
|
||||
*/
|
||||
public static void assertEqualMatrix(UnwritableMatrixIJK expected, UnwritableMatrixIJK actual) {
|
||||
assertEqualDouble(expected.getII(), actual.getII());
|
||||
assertEqualDouble(expected.getIJ(), actual.getIJ());
|
||||
assertEqualDouble(expected.getIK(), actual.getIK());
|
||||
assertEqualDouble(expected.getJI(), actual.getJI());
|
||||
assertEqualDouble(expected.getJJ(), actual.getJJ());
|
||||
assertEqualDouble(expected.getJK(), actual.getJK());
|
||||
assertEqualDouble(expected.getKI(), actual.getKI());
|
||||
assertEqualDouble(expected.getKJ(), actual.getKJ());
|
||||
assertEqualDouble(expected.getKK(), actual.getKK());
|
||||
}
|
||||
|
||||
public static void assertComponentEquals(String message, UnwritableMatrixIJK expected,
|
||||
UnwritableMatrixIJK actual, double delta) {
|
||||
assertEquals(message, expected.getII(), actual.getII(), delta);
|
||||
assertEquals(message, expected.getJI(), actual.getJI(), delta);
|
||||
assertEquals(message, expected.getKI(), actual.getKI(), delta);
|
||||
assertEquals(message, expected.getIJ(), actual.getIJ(), delta);
|
||||
assertEquals(message, expected.getJJ(), actual.getJJ(), delta);
|
||||
assertEquals(message, expected.getKJ(), actual.getKJ(), delta);
|
||||
assertEquals(message, expected.getIK(), actual.getIK(), delta);
|
||||
assertEquals(message, expected.getJK(), actual.getJK(), delta);
|
||||
assertEquals(message, expected.getKK(), actual.getKK(), delta);
|
||||
}
|
||||
|
||||
public static void assertComponentEquals(UnwritableMatrixIJK expected, UnwritableMatrixIJK actual,
|
||||
double delta) {
|
||||
assertEquals(expected.getII(), actual.getII(), delta);
|
||||
assertEquals(expected.getJI(), actual.getJI(), delta);
|
||||
assertEquals(expected.getKI(), actual.getKI(), delta);
|
||||
assertEquals(expected.getIJ(), actual.getIJ(), delta);
|
||||
assertEquals(expected.getJJ(), actual.getJJ(), delta);
|
||||
assertEquals(expected.getKJ(), actual.getKJ(), delta);
|
||||
assertEquals(expected.getIK(), actual.getIK(), delta);
|
||||
assertEquals(expected.getJK(), actual.getJK(), delta);
|
||||
assertEquals(expected.getKK(), actual.getKK(), delta);
|
||||
}
|
||||
|
||||
public static void assertComponentRelativeEquality(UnwritableMatrixIJK expected,
|
||||
UnwritableMatrixIJK actual, double delta) {
|
||||
assertRelativeEquality(expected.getII(), actual.getII(), delta);
|
||||
assertRelativeEquality(expected.getJI(), actual.getJI(), delta);
|
||||
assertRelativeEquality(expected.getKI(), actual.getKI(), delta);
|
||||
assertRelativeEquality(expected.getIJ(), actual.getIJ(), delta);
|
||||
assertRelativeEquality(expected.getJJ(), actual.getJJ(), delta);
|
||||
assertRelativeEquality(expected.getKJ(), actual.getKJ(), delta);
|
||||
assertRelativeEquality(expected.getIK(), actual.getIK(), delta);
|
||||
assertRelativeEquality(expected.getJK(), actual.getJK(), delta);
|
||||
assertRelativeEquality(expected.getKK(), actual.getKK(), delta);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert two vectors are equivalent.
|
||||
*
|
||||
* @param expected Expected vector.
|
||||
* @param actual Actual vector.
|
||||
*/
|
||||
public static void assertEquivalentVector(UnwritableVectorIJK expected,
|
||||
UnwritableVectorIJK actual) {
|
||||
assertEquivalentDouble(expected.getI(), actual.getI());
|
||||
assertEquivalentDouble(expected.getJ(), actual.getJ());
|
||||
assertEquivalentDouble(expected.getK(), actual.getK());
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert two vectors are exactly equal.
|
||||
*
|
||||
* @param expected Expected vector.
|
||||
* @param actual Actual vector.
|
||||
*/
|
||||
public static void assertEqualVector(UnwritableVectorIJK expected, UnwritableVectorIJK actual) {
|
||||
assertEqualDouble(expected.getI(), actual.getI());
|
||||
assertEqualDouble(expected.getJ(), actual.getJ());
|
||||
assertEqualDouble(expected.getK(), actual.getK());
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert component-wise equality with tolerance.
|
||||
*
|
||||
* @param expected Expected vector.
|
||||
* @param actual Actual vector.
|
||||
* @param delta Component-wise delta factor.
|
||||
*/
|
||||
public static void assertComponentEquals(UnwritableVectorIJK expected, UnwritableVectorIJK actual,
|
||||
double delta) {
|
||||
assertEquals(expected.getI(), actual.getI(), delta);
|
||||
assertEquals(expected.getJ(), actual.getJ(), delta);
|
||||
assertEquals(expected.getK(), actual.getK(), delta);
|
||||
}
|
||||
|
||||
public static void assertComponentRelativeEquality(UnwritableVectorIJK expected,
|
||||
UnwritableVectorIJK actual, double delta) {
|
||||
assertRelativeEquality(expected.getI(), actual.getI(), delta);
|
||||
assertRelativeEquality(expected.getJ(), actual.getJ(), delta);
|
||||
assertRelativeEquality(expected.getK(), actual.getK(), delta);
|
||||
}
|
||||
|
||||
public static void assertRotationAngleEquals(UnwritableRotationMatrixIJK expected,
|
||||
UnwritableRotationMatrixIJK actual, double toleranceInRadians) {
|
||||
assertEquals(0.0,
|
||||
Math.abs(new AxisAndAngle(RotationMatrixIJK.mtxm(actual, expected)).getAngle()),
|
||||
toleranceInRadians);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert two matrices are equivalent.
|
||||
*
|
||||
* @param expected Expected matrix.
|
||||
* @param actual Actual matrix.
|
||||
*/
|
||||
public static void assertEquivalentMatrix(UnwritableMatrixIJ expected,
|
||||
UnwritableMatrixIJ actual) {
|
||||
assertEquivalentDouble(expected.getII(), actual.getII());
|
||||
assertEquivalentDouble(expected.getIJ(), actual.getIJ());
|
||||
assertEquivalentDouble(expected.getJI(), actual.getJI());
|
||||
assertEquivalentDouble(expected.getJJ(), actual.getJJ());
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert two matrices are exactly equal.
|
||||
*
|
||||
* @param expected Expected matrix.
|
||||
* @param actual Actual matrix.
|
||||
*/
|
||||
public static void assertEqualMatrix(UnwritableMatrixIJ expected, UnwritableMatrixIJ actual) {
|
||||
assertEqualDouble(expected.getII(), actual.getII());
|
||||
assertEqualDouble(expected.getIJ(), actual.getIJ());
|
||||
assertEqualDouble(expected.getJI(), actual.getJI());
|
||||
assertEqualDouble(expected.getJJ(), actual.getJJ());
|
||||
}
|
||||
|
||||
public static void assertComponentEquals(String message, UnwritableMatrixIJ expected,
|
||||
UnwritableMatrixIJ actual, double delta) {
|
||||
assertEquals(message, expected.getII(), actual.getII(), delta);
|
||||
assertEquals(message, expected.getJI(), actual.getJI(), delta);
|
||||
assertEquals(message, expected.getIJ(), actual.getIJ(), delta);
|
||||
assertEquals(message, expected.getJJ(), actual.getJJ(), delta);
|
||||
}
|
||||
|
||||
public static void assertComponentEquals(UnwritableMatrixIJ expected, UnwritableMatrixIJ actual,
|
||||
double delta) {
|
||||
assertEquals(expected.getII(), actual.getII(), delta);
|
||||
assertEquals(expected.getJI(), actual.getJI(), delta);
|
||||
assertEquals(expected.getIJ(), actual.getIJ(), delta);
|
||||
assertEquals(expected.getJJ(), actual.getJJ(), delta);
|
||||
}
|
||||
|
||||
public static void assertComponentRelativeEquality(UnwritableMatrixIJ expected,
|
||||
UnwritableMatrixIJ actual, double delta) {
|
||||
assertRelativeEquality(expected.getII(), actual.getII(), delta);
|
||||
assertRelativeEquality(expected.getJI(), actual.getJI(), delta);
|
||||
assertRelativeEquality(expected.getIJ(), actual.getIJ(), delta);
|
||||
assertRelativeEquality(expected.getJJ(), actual.getJJ(), delta);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert two vectors are equivalent.
|
||||
*
|
||||
* @param expected Expected vector.
|
||||
* @param actual Actual vector.
|
||||
*/
|
||||
public static void assertEquivalentVector(UnwritableVectorIJ expected,
|
||||
UnwritableVectorIJ actual) {
|
||||
assertEquivalentDouble(expected.getI(), actual.getI());
|
||||
assertEquivalentDouble(expected.getJ(), actual.getJ());
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert two vectors are exactly equal.
|
||||
*
|
||||
* @param expected Expected vector.
|
||||
* @param actual Actual vector.
|
||||
*/
|
||||
public static void assertEqualVector(UnwritableVectorIJ expected, UnwritableVectorIJ actual) {
|
||||
assertEqualDouble(expected.getI(), actual.getI());
|
||||
assertEqualDouble(expected.getJ(), actual.getJ());
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert component-wise equality with tolerance.
|
||||
*
|
||||
* @param expected Expected vector.
|
||||
* @param actual Actual vector.
|
||||
* @param delta Component-wise delta factor.
|
||||
*/
|
||||
public static void assertComponentEquals(UnwritableVectorIJ expected, UnwritableVectorIJ actual,
|
||||
double delta) {
|
||||
assertEquals(expected.getI(), actual.getI(), delta);
|
||||
assertEquals(expected.getJ(), actual.getJ(), delta);
|
||||
}
|
||||
|
||||
public static void assertComponentRelativeEquality(UnwritableVectorIJ expected,
|
||||
UnwritableVectorIJ actual, double delta) {
|
||||
assertRelativeEquality(expected.getI(), actual.getI(), delta);
|
||||
assertRelativeEquality(expected.getJ(), actual.getJ(), delta);
|
||||
}
|
||||
|
||||
public static void assertEquivalentStateTransform(UnwritableStateTransform expected,
|
||||
UnwritableStateTransform actual) {
|
||||
assertEquivalentMatrix(expected.getRotation(), actual.getRotation());
|
||||
assertEquivalentMatrix(expected.getRotationDerivative(), actual.getRotationDerivative());
|
||||
}
|
||||
|
||||
public static void assertEqualStateTransform(UnwritableStateTransform expected,
|
||||
UnwritableStateTransform actual) {
|
||||
assertEqualMatrix(expected.getRotation(), actual.getRotation());
|
||||
assertEqualMatrix(expected.getRotationDerivative(), actual.getRotationDerivative());
|
||||
}
|
||||
|
||||
public static void assertComponentEquals(UnwritableStateTransform expected,
|
||||
UnwritableStateTransform actual, double delta) {
|
||||
assertComponentEquals(expected.getRotation(), actual.getRotation(), delta);
|
||||
assertComponentEquals(expected.getRotationDerivative(), actual.getRotationDerivative(), delta);
|
||||
}
|
||||
|
||||
public static void assertComponentEquals(String message, UnwritableStateTransform expected,
|
||||
UnwritableStateTransform actual, double delta) {
|
||||
assertComponentEquals(message, expected.getRotation(), actual.getRotation(), delta);
|
||||
assertComponentEquals(message, expected.getRotationDerivative(), actual.getRotationDerivative(),
|
||||
delta);
|
||||
}
|
||||
|
||||
public static void assertComponentRelativeEquality(UnwritableStateTransform expected,
|
||||
UnwritableStateTransform actual, double delta) {
|
||||
assertComponentRelativeEquality(expected.getRotation(), actual.getRotation(), delta);
|
||||
assertComponentRelativeEquality(expected.getRotationDerivative(),
|
||||
actual.getRotationDerivative(), delta);
|
||||
}
|
||||
|
||||
public static void assertEquivalentStateVector(UnwritableStateVector expected,
|
||||
UnwritableStateVector actual) {
|
||||
assertEquivalentVector(expected.getPosition(), actual.getPosition());
|
||||
assertEquivalentVector(expected.getVelocity(), actual.getVelocity());
|
||||
}
|
||||
|
||||
public static void assertEqualStateVector(UnwritableStateVector expected,
|
||||
UnwritableStateVector actual) {
|
||||
assertEqualVector(expected.getPosition(), actual.getPosition());
|
||||
assertEqualVector(expected.getVelocity(), actual.getVelocity());
|
||||
}
|
||||
|
||||
public static void assertComponentEquals(UnwritableStateVector expected,
|
||||
UnwritableStateVector actual, double delta) {
|
||||
assertComponentEquals(expected.getPosition(), actual.getPosition(), delta);
|
||||
assertComponentEquals(expected.getVelocity(), actual.getVelocity(), delta);
|
||||
}
|
||||
|
||||
public static void assertComponentRelativeEquality(UnwritableStateVector expected,
|
||||
UnwritableStateVector actual, double delta) {
|
||||
assertComponentRelativeEquality(expected.getPosition(), actual.getPosition(), delta);
|
||||
assertComponentRelativeEquality(expected.getVelocity(), actual.getVelocity(), delta);
|
||||
}
|
||||
|
||||
public static void assertEqualInterval(UnwritableInterval expected, UnwritableInterval actual,
|
||||
double tolerance) {
|
||||
assertEquals(expected.getBegin(), actual.getBegin(), tolerance);
|
||||
assertEquals(expected.getEnd(), actual.getEnd(), tolerance);
|
||||
}
|
||||
|
||||
public static void assertComponentEquals(Point2D expected, Point2D actual, double delta) {
|
||||
assertEquals(expected.getX(), actual.getX(), delta);
|
||||
assertEquals(expected.getY(), actual.getY(), delta);
|
||||
}
|
||||
|
||||
}
|
||||
154
src/main/java/picante/junit/CaptureAndAnswer.java
Normal file
154
src/main/java/picante/junit/CaptureAndAnswer.java
Normal file
@@ -0,0 +1,154 @@
|
||||
package picante.junit;
|
||||
|
||||
import org.easymock.Capture;
|
||||
import org.easymock.EasyMock;
|
||||
import org.easymock.IAnswer;
|
||||
import org.easymock.IExpectationSetters;
|
||||
import picante.designpatterns.Writable;
|
||||
|
||||
/**
|
||||
* This class exists to solve the problem in that arises when attempting to create mock
|
||||
* implementations of classes or interfaces implementing the "buffer pattern".
|
||||
* <p>
|
||||
* Specifically, if you have a class or interface that you want to mock that has methods that look
|
||||
* like this:
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* interface BufferPatternExample {
|
||||
*
|
||||
* public Value get(double otherArg, Value buffer);
|
||||
*
|
||||
* }
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* where the get method, by contract, is to mutate the contents of buffer and return a reference to
|
||||
* it for convenience, it turns out mocking can be a bit tricky.
|
||||
* </p>
|
||||
* <p>
|
||||
* This simple abstract class presents the solution to the problem:
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* BufferPatternExample mock = createMock(BufferPatternExample.class);
|
||||
*
|
||||
* CaptureAndAnswer<Value> capture = new CaptureAndAnswer<Value>() {
|
||||
* public void set(Value captured) {
|
||||
* ...mutate the contents of captured...
|
||||
* }
|
||||
* };
|
||||
*
|
||||
* expect(mock.get(eq(1.0), capture(capture.getCapture()))).andAnswer(capture);
|
||||
* replay(mock);
|
||||
*
|
||||
* ...execute test code that triggers method call...
|
||||
*
|
||||
* verify(mock);
|
||||
* </pre>
|
||||
*
|
||||
* </code>
|
||||
* <p>
|
||||
* Another, arguably awful, use case of this is for when the value is not returned. This is
|
||||
* counter-intuitive, as it requires a certain ordering of methods when interfacing with EasyMock.
|
||||
* Consider the following, similar interface:
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* interface BufferPatternExample2 {
|
||||
*
|
||||
* public void get(double otherArg, Value buffer);
|
||||
*
|
||||
* }
|
||||
* </code>
|
||||
* </pre>
|
||||
* <p>
|
||||
* Since the get function does not return a value, this complicates the "andAnswer" part
|
||||
* of the response. Fortunately EasyMock has a mechanism that provides for this, as an answer may
|
||||
* generate an exception, so the code will be invoked even if the return type is not present in the
|
||||
* function signature. For example:
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* BufferPatternExample2 mock = createMock(BufferPatternExample2.class);
|
||||
*
|
||||
* CaptureAndAnswer<Value> capture = new CaptureAndAnswer<Value>() {
|
||||
* public void set(Value captured) {
|
||||
* ...mutate the contents of captured...
|
||||
* }
|
||||
* };
|
||||
*
|
||||
* mock.get(eq(1.0), capture(capture.getCapture()));
|
||||
* expectLastCall().andAnswer(capture);
|
||||
* replay(mock);
|
||||
*
|
||||
* ...execute test code that triggers method call...
|
||||
*
|
||||
* verify(mock);
|
||||
* </code>
|
||||
* </pre>
|
||||
* <p>
|
||||
* Note: in our experience working with EasyMock the {@link IExpectationSetters#andAnswer(IAnswer)}
|
||||
* method must be invoked immediately after the {@link EasyMock#expectLastCall()} and prior to any
|
||||
* of the other chained method options from the {@link IExpectationSetters} interface.
|
||||
* </p>
|
||||
*
|
||||
* @author G.K.Stephens
|
||||
*
|
||||
* @param <T> the value of the buffer pattern function argument
|
||||
*/
|
||||
public abstract class CaptureAndAnswer<T> implements IAnswer<T> {
|
||||
|
||||
private final Capture<T> capture = EasyMock.newCapture();
|
||||
|
||||
/**
|
||||
* Method that mutates the contents of the capture into the desired result, simulating the effect
|
||||
* of executing the method.
|
||||
*
|
||||
* @param captured the captured argument
|
||||
*/
|
||||
public abstract void set(T captured);
|
||||
|
||||
@Override
|
||||
public T answer() throws Throwable {
|
||||
set(capture.getValue());
|
||||
return capture.getValue();
|
||||
}
|
||||
|
||||
public Capture<T> getCapture() {
|
||||
return capture;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link CaptureAndAnswer} for classes that implement the {@link Writable} interface.
|
||||
* <p>
|
||||
* Many of the classes and interfaces that are typically utilized in the buffer pattern this
|
||||
* supporting class is designed to enable mocking support. So, in most cases, this static creation
|
||||
* method should be sufficient to serve as a simple means of using this class.
|
||||
*
|
||||
* @param <U> The "parent" class or interface used in {@link Writable} interface
|
||||
* @param <W> The subclass of P that is to be captured.
|
||||
*
|
||||
* @param valueToSet the value to set when the mocked function is invoked.
|
||||
*
|
||||
* @return a newly created capture and answer
|
||||
*/
|
||||
public static <U, W extends Writable<? super U, W>> CaptureAndAnswer<W> create(
|
||||
final U valueToSet) {
|
||||
|
||||
return new CaptureAndAnswer<W>() {
|
||||
|
||||
@Override
|
||||
public void set(W captured) {
|
||||
captured.setTo(valueToSet);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
31
src/main/java/picante/math/CoordUtilities.java
Normal file
31
src/main/java/picante/math/CoordUtilities.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package picante.math;
|
||||
|
||||
/**
|
||||
*
|
||||
* Created : Jul 31, 2008 1:50:58 PM
|
||||
*
|
||||
* @author vandejd1
|
||||
*/
|
||||
public class CoordUtilities {
|
||||
|
||||
public static double convertLongitudeInRadiansToLocaltimeInFractionalHours(double longInRads) {
|
||||
// I want to know how far around past the -X axis this vector is, and
|
||||
// the longitude tells me how far around from +X it is, so I first
|
||||
// subtract off 180 degrees.
|
||||
|
||||
longInRads -= Math.PI;
|
||||
|
||||
// make sure the longitude is from 0 to 2*Pi:
|
||||
double TWOPI = Math.PI * 2.0;
|
||||
while (longInRads < 0) {
|
||||
longInRads += TWOPI;
|
||||
}
|
||||
while (longInRads > TWOPI) {
|
||||
longInRads -= TWOPI;
|
||||
}
|
||||
|
||||
// now convert to hours:
|
||||
return longInRads / TWOPI * 24.0;
|
||||
}
|
||||
|
||||
}
|
||||
570
src/main/java/picante/math/PicanteMath.java
Normal file
570
src/main/java/picante/math/PicanteMath.java
Normal file
@@ -0,0 +1,570 @@
|
||||
package picante.math;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import net.jafama.FastMath;
|
||||
|
||||
/**
|
||||
* This defines the standard set of Math methods to be used throughout the Picante library. This
|
||||
* class was created because there are 3rd party implementations of standard math methods that are
|
||||
* much faster compared to the standard JDK (java.lang.Math) methods.
|
||||
* <p>
|
||||
* As of now, the implementation that we have chosen in
|
||||
* <a href="https://github.com/jeffhain/jafama">JAFAMA</a>
|
||||
* <p>
|
||||
* I took the method comments from {@link FastMath}.
|
||||
*
|
||||
* @author G.K.Stephens
|
||||
*
|
||||
*/
|
||||
public class PicanteMath {
|
||||
|
||||
/*
|
||||
* Switches to the JDK (java.lang.Math) instead. As this should be only be set to true once, and
|
||||
* because making this lockable would have performance implications, I choose to make this
|
||||
* volatile instead of using AtomicBoolean.
|
||||
*/
|
||||
private static volatile boolean USE_JDK_MATH = false;
|
||||
|
||||
/**
|
||||
* Constructor should be private
|
||||
*/
|
||||
private PicanteMath() {};
|
||||
|
||||
/**
|
||||
* Calling this method uses the standard JDK (java.lang.Math) methods instead of the preferred
|
||||
* Crucible math methods. This is intended to be called once at the beginning of the application.
|
||||
* Therefore, and because of performance considerations, this has not been implemented in a thread
|
||||
* safe way. To encourage safe use with this method, no method has been provided to switch back.
|
||||
*/
|
||||
public static void useJdkMath() {
|
||||
USE_JDK_MATH = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This shouldn't be called, it is only here for unit testing.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static void useFastMath() {
|
||||
USE_JDK_MATH = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@code double} value that is closer than any other to <i>e</i>, the base of the natural
|
||||
* logarithms.
|
||||
*/
|
||||
public static final double E = Math.E;
|
||||
|
||||
/**
|
||||
* The {@code double} value that is closer than any other to <i>pi</i>, the ratio of the
|
||||
* circumference of a circle to its diameter.
|
||||
*/
|
||||
public static final double PI = Math.PI;
|
||||
|
||||
/**
|
||||
* @param angle Angle in radians.
|
||||
* @return Angle sine.
|
||||
*/
|
||||
public static double sin(double a) {
|
||||
if (USE_JDK_MATH) {
|
||||
return Math.sin(a);
|
||||
}
|
||||
return FastMath.sin(a);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param angle Angle in radians.
|
||||
* @return Angle cosine.
|
||||
*/
|
||||
public static double cos(double a) {
|
||||
if (USE_JDK_MATH) {
|
||||
return Math.cos(a);
|
||||
}
|
||||
return FastMath.cos(a);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can have very bad relative error near +-PI/2, but of the same magnitude than the relative delta
|
||||
* between StrictMath.tan(PI/2) and StrictMath.tan(nextDown(PI/2)).
|
||||
*
|
||||
* @param angle Angle in radians.
|
||||
* @return Angle tangent.
|
||||
*/
|
||||
public static double tan(double a) {
|
||||
if (USE_JDK_MATH) {
|
||||
return Math.tan(a);
|
||||
}
|
||||
return FastMath.tan(a);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value Value in [-1,1].
|
||||
* @return Value arcsine, in radians, in [-PI/2,PI/2].
|
||||
*/
|
||||
public static double asin(double a) {
|
||||
if (USE_JDK_MATH) {
|
||||
return Math.asin(a);
|
||||
}
|
||||
return FastMath.asin(a);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value Value in [-1,1].
|
||||
* @return Value arccosine, in radians, in [0,PI].
|
||||
*/
|
||||
public static double acos(double a) {
|
||||
if (USE_JDK_MATH) {
|
||||
return Math.acos(a);
|
||||
}
|
||||
return FastMath.acos(a);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value A double value.
|
||||
* @return Value arctangent, in radians, in [-PI/2,PI/2].
|
||||
*/
|
||||
public static double atan(double a) {
|
||||
if (USE_JDK_MATH) {
|
||||
return Math.atan(a);
|
||||
}
|
||||
return FastMath.atan(a);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives same result as Math.toRadians for some particular values like 90.0, 180.0 or 360.0, but
|
||||
* is faster (no division).
|
||||
*
|
||||
* @param angdeg Angle value in degrees.
|
||||
* @return Angle value in radians.
|
||||
*/
|
||||
public static double toRadians(double angdeg) {
|
||||
if (USE_JDK_MATH) {
|
||||
return Math.toRadians(angdeg);
|
||||
}
|
||||
return FastMath.toRadians(angdeg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives same result as Math.toDegrees for some particular values like Math.PI/2, Math.PI or
|
||||
* 2*Math.PI, but is faster (no division).
|
||||
*
|
||||
* @param angrad Angle value in radians.
|
||||
* @return Angle value in degrees.
|
||||
*/
|
||||
public static double toDegrees(double angrad) {
|
||||
if (USE_JDK_MATH) {
|
||||
return Math.toDegrees(angrad);
|
||||
}
|
||||
return FastMath.toDegrees(angrad);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value A double value.
|
||||
* @return e^value.
|
||||
*/
|
||||
public static double exp(double a) {
|
||||
if (USE_JDK_MATH) {
|
||||
return Math.exp(a);
|
||||
}
|
||||
return FastMath.exp(a);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value A double value.
|
||||
* @return Value logarithm (base e).
|
||||
*/
|
||||
public static double log(double a) {
|
||||
if (USE_JDK_MATH) {
|
||||
return Math.log(a);
|
||||
}
|
||||
return FastMath.log(a);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value A double value.
|
||||
* @return Value logarithm (base 10).
|
||||
*/
|
||||
public static double log10(double a) {
|
||||
if (USE_JDK_MATH) {
|
||||
return Math.log10(a);
|
||||
}
|
||||
return FastMath.log10(a);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value A double value.
|
||||
* @return 1+Value logarithm (base e).
|
||||
*/
|
||||
public static double log1p(double a) {
|
||||
if (USE_JDK_MATH) {
|
||||
return Math.log1p(a);
|
||||
}
|
||||
return FastMath.log1p(a);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value A double value.
|
||||
* @return Value square root.
|
||||
*/
|
||||
public static double sqrt(double a) {
|
||||
if (USE_JDK_MATH) {
|
||||
return Math.sqrt(a);
|
||||
}
|
||||
return FastMath.sqrt(a);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value A double value.
|
||||
* @return Value cubic root.
|
||||
*/
|
||||
public static double cbrt(double a) {
|
||||
if (USE_JDK_MATH) {
|
||||
return Math.cbrt(a);
|
||||
}
|
||||
return FastMath.cbrt(a);
|
||||
}
|
||||
|
||||
public static double IEEEremainder(double f1, double f2) {
|
||||
if (USE_JDK_MATH) {
|
||||
return Math.IEEEremainder(f1, f2);
|
||||
}
|
||||
return FastMath.IEEEremainder(f1, f2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value A double value.
|
||||
* @return Ceiling of value.
|
||||
*/
|
||||
public static double ceil(double a) {
|
||||
if (USE_JDK_MATH) {
|
||||
return Math.ceil(a);
|
||||
}
|
||||
return FastMath.ceil(a);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value A double value.
|
||||
* @return Floor of value.
|
||||
*/
|
||||
public static double floor(double a) {
|
||||
if (USE_JDK_MATH) {
|
||||
return Math.floor(a);
|
||||
}
|
||||
return FastMath.floor(a);
|
||||
}
|
||||
|
||||
/**
|
||||
* For special values for which multiple conventions could be adopted, behaves like
|
||||
* Math.atan2(double,double).
|
||||
*
|
||||
* @param y Coordinate on y axis.
|
||||
* @param x Coordinate on x axis.
|
||||
* @return Angle from x axis positive side to (x,y) position, in radians, in [-PI,PI]. Angle
|
||||
* measure is positive when going from x axis to y axis (positive sides).
|
||||
*/
|
||||
public static double atan2(double y, double x) {
|
||||
if (USE_JDK_MATH) {
|
||||
return Math.atan2(y, x);
|
||||
}
|
||||
return FastMath.atan2(y, x);
|
||||
}
|
||||
|
||||
/**
|
||||
* 1e-13ish accuracy or better on whole double range.
|
||||
*
|
||||
* @param value A double value.
|
||||
* @param power A power.
|
||||
* @return value^power.
|
||||
*/
|
||||
public static double pow(double a, double b) {
|
||||
if (USE_JDK_MATH) {
|
||||
return Math.pow(a, b);
|
||||
}
|
||||
return FastMath.pow(a, b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Might have different semantics than Math.round(double), see bugs 6430675 and 8010430.
|
||||
*
|
||||
* @param value A double value.
|
||||
* @return Value rounded to nearest long, choosing superior long in case two are equally close
|
||||
* (i.e. rounding-up).
|
||||
*/
|
||||
public static long round(double a) {
|
||||
if (USE_JDK_MATH) {
|
||||
return Math.round(a);
|
||||
}
|
||||
return FastMath.round(a);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value An int value.
|
||||
* @return The absolute value, except if value is Integer.MIN_VALUE, for which it returns
|
||||
* Integer.MIN_VALUE.
|
||||
*/
|
||||
public static int abs(int a) {
|
||||
return Math.abs(a);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the absolute value of a {@code double} value. If the argument is not negative, the
|
||||
* argument is returned. If the argument is negative, the negation of the argument is returned.
|
||||
* Special cases:
|
||||
* <ul>
|
||||
* <li>If the argument is positive zero or negative zero, the result is positive zero.
|
||||
* <li>If the argument is infinite, the result is positive infinity.
|
||||
* <li>If the argument is NaN, the result is NaN.
|
||||
* </ul>
|
||||
* In other words, the result is the same as the value of the expression:
|
||||
* <p>
|
||||
* {@code Double.longBitsToDouble((Double.doubleToLongBits(a)<<1)>>>1)}
|
||||
*
|
||||
* @param a the argument whose absolute value is to be determined
|
||||
* @return the absolute value of the argument.
|
||||
*/
|
||||
public static double abs(double a) {
|
||||
if (USE_JDK_MATH) {
|
||||
return Math.abs(a);
|
||||
}
|
||||
return FastMath.abs(a);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the greater of two {@code int} values. That is, the result is the argument closer to
|
||||
* the value of {@link Integer#MAX_VALUE}. If the arguments have the same value, the result is
|
||||
* that same value.
|
||||
*
|
||||
* @param a an argument.
|
||||
* @param b another argument.
|
||||
* @return the larger of {@code a} and {@code b}.
|
||||
*/
|
||||
public static int max(int a, int b) {
|
||||
return Math.max(a, b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the greater of two {@code double} values. That is, the result is the argument closer to
|
||||
* positive infinity. If the arguments have the same value, the result is that same value. If
|
||||
* either value is NaN, then the result is NaN. Unlike the numerical comparison operators, this
|
||||
* method considers negative zero to be strictly smaller than positive zero. If one argument is
|
||||
* positive zero and the other negative zero, the result is positive zero.
|
||||
*
|
||||
* @param a an argument.
|
||||
* @param b another argument.
|
||||
* @return the larger of {@code a} and {@code b}.
|
||||
*/
|
||||
public static double max(double a, double b) {
|
||||
if (USE_JDK_MATH) {
|
||||
return Math.max(a, b);
|
||||
}
|
||||
return FastMath.max(a, b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the smaller of two {@code int} values. That is, the result the argument closer to the
|
||||
* value of {@link Integer#MIN_VALUE}. If the arguments have the same value, the result is that
|
||||
* same value.
|
||||
*
|
||||
* @param a an argument.
|
||||
* @param b another argument.
|
||||
* @return the smaller of {@code a} and {@code b}.
|
||||
*/
|
||||
public static int min(int a, int b) {
|
||||
return Math.min(a, b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the smaller of two {@code double} values. That is, the result is the value closer to
|
||||
* negative infinity. If the arguments have the same value, the result is that same value. If
|
||||
* either value is NaN, then the result is NaN. Unlike the numerical comparison operators, this
|
||||
* method considers negative zero to be strictly smaller than positive zero. If one argument is
|
||||
* positive zero and the other is negative zero, the result is negative zero.
|
||||
*
|
||||
* @param a an argument.
|
||||
* @param b another argument.
|
||||
* @return the smaller of {@code a} and {@code b}.
|
||||
*/
|
||||
public static double min(double a, double b) {
|
||||
if (USE_JDK_MATH) {
|
||||
return Math.min(a, b);
|
||||
}
|
||||
return FastMath.min(a, b);
|
||||
}
|
||||
|
||||
/**
|
||||
* The ULP (Unit in the Last Place) is the distance to the next value larger in magnitude.
|
||||
*
|
||||
* @param value A double value.
|
||||
* @return The size of an ulp of the specified value, or Double.MIN_VALUE if it is +-0.0, or
|
||||
* +Infinity if it is +-Infinity, or NaN if it is NaN.
|
||||
*/
|
||||
public static double ulp(double d) {
|
||||
if (USE_JDK_MATH) {
|
||||
return Math.ulp(d);
|
||||
}
|
||||
return FastMath.ulp(d);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value A double value.
|
||||
* @return -1.0 if the specified value is < 0, 1.0 if it is > 0, and the value itself if it is NaN
|
||||
* or +-0.0.
|
||||
*/
|
||||
public static double signum(double d) {
|
||||
if (USE_JDK_MATH) {
|
||||
return Math.signum(d);
|
||||
}
|
||||
return FastMath.signum(d);
|
||||
}
|
||||
|
||||
/**
|
||||
* Some properties of sinh(x) = (exp(x)-exp(-x))/2:
|
||||
*
|
||||
* <pre>
|
||||
* 1) defined on ]-Infinity,+Infinity[
|
||||
* 2) result in ]-Infinity,+Infinity[
|
||||
* 3) sinh(x) = -sinh(-x) (implies sinh(0) = 0)
|
||||
* 4) sinh(epsilon) ~= epsilon
|
||||
* 5) lim(sinh(x),x->+Infinity) = +Infinity
|
||||
* (y increasing exponentially faster than x)
|
||||
* 6) reaches +Infinity (double overflow) for x >= 710.475860073944,
|
||||
* i.e. a bit further than exp(x)
|
||||
* </pre>
|
||||
*
|
||||
* @param x A double value.
|
||||
* @return Value hyperbolic sine.
|
||||
*/
|
||||
public static double sinh(double x) {
|
||||
if (USE_JDK_MATH) {
|
||||
return Math.sinh(x);
|
||||
}
|
||||
return FastMath.sinh(x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Some properties of cosh(x) = (exp(x)+exp(-x))/2:
|
||||
*
|
||||
* <pre>
|
||||
* 1) defined on ]-Infinity,+Infinity[
|
||||
* 2) result in [1,+Infinity[
|
||||
* 3) cosh(0) = 1
|
||||
* 4) cosh(x) = cosh(-x)
|
||||
* 5) lim(cosh(x),x->+Infinity) = +Infinity
|
||||
* (y increasing exponentially faster than x)
|
||||
* 6) reaches +Infinity (double overflow) for x >= 710.475860073944,
|
||||
* i.e. a bit further than exp(x)
|
||||
* </pre>
|
||||
*
|
||||
* @param x A double value.
|
||||
* @return Value hyperbolic cosine.
|
||||
*/
|
||||
public static double cosh(double x) {
|
||||
if (USE_JDK_MATH) {
|
||||
return Math.cosh(x);
|
||||
}
|
||||
return FastMath.cosh(x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Some properties of tanh(x) = sinh(x)/cosh(x) = (exp(2*x)-1)/(exp(2*x)+1):
|
||||
*
|
||||
* <pre>
|
||||
* 1) defined on ]-Infinity,+Infinity[
|
||||
* 2) result in ]-1,1[
|
||||
* 3) tanh(x) = -tanh(-x) (implies tanh(0) = 0)
|
||||
* 4) tanh(epsilon) ~= epsilon
|
||||
* 5) lim(tanh(x),x->+Infinity) = 1
|
||||
* 6) reaches 1 (double loss of precision) for x = 19.061547465398498
|
||||
* </pre>
|
||||
*
|
||||
* @param x A double value.
|
||||
* @return Value hyperbolic tangent.
|
||||
*/
|
||||
public static double tanh(double x) {
|
||||
if (USE_JDK_MATH) {
|
||||
return Math.tanh(x);
|
||||
}
|
||||
return FastMath.tanh(x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Some properties of acosh(x) = log(x + sqrt(x^2 - 1)):
|
||||
*
|
||||
* <pre>
|
||||
* 1) defined on [1,+Infinity[
|
||||
* 2) result in ]0,+Infinity[ (by convention, since cosh(x) = cosh(-x))
|
||||
* 3) acosh(1) = 0
|
||||
* 4) acosh(1+epsilon) ~= log(1 + sqrt(2*epsilon)) ~= sqrt(2*epsilon)
|
||||
* 5) lim(acosh(x),x->+Infinity) = +Infinity
|
||||
* (y increasing logarithmically slower than x)
|
||||
* </pre>
|
||||
*
|
||||
* @param x A double value.
|
||||
* @return Value hyperbolic arccosine.
|
||||
*/
|
||||
public static double acosh(double x) {
|
||||
return FastMath.acosh(x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Some properties of asinh(x) = log(x + sqrt(x^2 + 1)):
|
||||
*
|
||||
* <pre>
|
||||
* 1) defined on ]-Infinity,+Infinity[
|
||||
* 2) result in ]-Infinity,+Infinity[
|
||||
* 3) asinh(x) = -asinh(-x) (implies asinh(0) = 0)
|
||||
* 4) asinh(epsilon) ~= epsilon
|
||||
* 5) lim(asinh(x),x->+Infinity) = +Infinity
|
||||
* (y increasing logarithmically slower than x)
|
||||
* </pre>
|
||||
*
|
||||
* @param x A double value.
|
||||
* @return Value hyperbolic arcsine.
|
||||
*/
|
||||
public static double asinh(double x) {
|
||||
return FastMath.asinh(x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Some properties of atanh(x) = log((1+x)/(1-x))/2:
|
||||
*
|
||||
* <pre>
|
||||
* 1) defined on ]-1,1[
|
||||
* 2) result in ]-Infinity,+Infinity[
|
||||
* 3) atanh(-1) = -Infinity (by continuity)
|
||||
* 4) atanh(1) = +Infinity (by continuity)
|
||||
* 5) atanh(epsilon) ~= epsilon
|
||||
* 6) lim(atanh(x),x->1) = +Infinity
|
||||
* </pre>
|
||||
*
|
||||
* @param x A double value.
|
||||
* @return Value hyperbolic arctangent.
|
||||
*/
|
||||
public static double atanh(double x) {
|
||||
return FastMath.atanh(x);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return sqrt(x^2+y^2) without intermediate overflow or underflow.
|
||||
*/
|
||||
public static double hypot(double x, double y) {
|
||||
if (USE_JDK_MATH) {
|
||||
return Math.hypot(x, y);
|
||||
}
|
||||
return FastMath.hypot(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param x A double value.
|
||||
* @return The double mathematical integer closest to the specified value, choosing even one if
|
||||
* two are equally close, or respectively NaN, +-Infinity or +-0.0 if the value is any of
|
||||
* these.
|
||||
*/
|
||||
public static double rint(double x) {
|
||||
if (USE_JDK_MATH) {
|
||||
return Math.rint(x);
|
||||
}
|
||||
return FastMath.rint(x);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
729
src/main/java/picante/math/Statistics.java
Normal file
729
src/main/java/picante/math/Statistics.java
Normal file
@@ -0,0 +1,729 @@
|
||||
package picante.math;
|
||||
|
||||
import static picante.math.PicanteMath.pow;
|
||||
import static picante.math.PicanteMath.sqrt;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Lists;
|
||||
import picante.designpatterns.BuildFailedException;
|
||||
|
||||
/**
|
||||
* Immutable class capturing statistics of a sequence of double precision numbers.
|
||||
* <p>
|
||||
* The constructors of this class are private and as such are unavailable for use. {@link Builder}
|
||||
* is used to create instances of this class, as are the static "create" convenience
|
||||
* methods present on this class.
|
||||
* </p>
|
||||
* <p>
|
||||
* The value of variance, standard deviation, skewness and kurtosis may be either computed with the
|
||||
* defining, biased operations or with the unbiased ones. See
|
||||
* {@link Builder#useEstimator(Estimator)} for details.
|
||||
* </p>
|
||||
* <p>
|
||||
* When a statistics instance is built, an estimator and a index tracker are utilized implicitly in
|
||||
* its construction. These enumerations are captured in the instance for reference purposes and
|
||||
* available via the methods {@link Statistics#getEstimator()} and {@link Statistics#getTracker()}.
|
||||
* </p>
|
||||
*
|
||||
* @see <a href="doc-files/statistics.pdf">Statistics Reference Material</a>
|
||||
*/
|
||||
public final class Statistics {
|
||||
|
||||
/**
|
||||
* Enumeration used to control how the builder tracks the indices indicating the location of the
|
||||
* extrema from the sequence of doubles. This exists largely as a memory optimization. If you are
|
||||
* analyzing large sequences of fixed values it may not make sense to capture the locations of the
|
||||
* extrema as it may require a large amount of memory.
|
||||
*/
|
||||
public enum Tracker {
|
||||
|
||||
/**
|
||||
* Provides absolutely no tracking of the minimum or maximum indices in the sequence of
|
||||
* accumulated doubles.
|
||||
*/
|
||||
NONE {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Override
|
||||
void record(List<Integer> indices, double oldValue, double newValue, int newIndex) {}
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Tracks only the first occurrence of the minimum or maximum value's location in the sequence
|
||||
* of accumulated doubles.
|
||||
*/
|
||||
FIRST {
|
||||
|
||||
@Override
|
||||
void record(List<Integer> indices, double oldValue, double newValue, int newIndex) {
|
||||
if (indices.size() == 0) {
|
||||
indices.add(0, newIndex);
|
||||
return;
|
||||
}
|
||||
if (oldValue != newValue) {
|
||||
indices.set(0, newIndex);
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Tracks only the final occurrence of the minimum or maximum value's location in the sequence
|
||||
* of accumulated doubles.
|
||||
*/
|
||||
LAST {
|
||||
|
||||
@Override
|
||||
void record(List<Integer> indices, @SuppressWarnings("unused") double oldValue,
|
||||
@SuppressWarnings("unused") double newValue, int newIndex) {
|
||||
if (indices.size() == 0) {
|
||||
indices.add(0, newIndex);
|
||||
return;
|
||||
}
|
||||
indices.set(0, newIndex);
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Tracks the location of all minimum or maximum value's location in the sequence of accumulated
|
||||
* doubles.
|
||||
*/
|
||||
ALL {
|
||||
|
||||
@Override
|
||||
void record(List<Integer> indices, double oldValue, double newValue, int newIndex) {
|
||||
if (newValue != oldValue) {
|
||||
indices.clear();
|
||||
}
|
||||
indices.add(newIndex);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Record an extrema's location, if appropriate. This method should only be invoked when a new
|
||||
* or equivalent extrema is encountered.
|
||||
*
|
||||
* @param indices the mutable list of indices of previously recorded extrema locations
|
||||
* @param oldValue the old extrema
|
||||
* @param newValue the new extrema
|
||||
* @param newIndex the index of the new extrema
|
||||
*/
|
||||
abstract void record(List<Integer> indices, double oldValue, double newValue, int newIndex);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumeration capturing the various options with regards to computing higher order statistics of
|
||||
* the sequence of doubles.
|
||||
*/
|
||||
public enum Estimator {
|
||||
|
||||
/**
|
||||
* Instructs the builder to compute "unbiased" statistics assuming the sequence of
|
||||
* doubles is a sample of a larger population.
|
||||
*/
|
||||
SAMPLE {
|
||||
@Override
|
||||
double estimateVariance(Builder builder) {
|
||||
return builder.m2 / (builder.n - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
double estimateSkewness(Builder builder) {
|
||||
return builder.n * sqrt(builder.n - 1) / (builder.n - 2) * builder.m3
|
||||
/ pow(builder.m2, 1.5);
|
||||
}
|
||||
|
||||
@Override
|
||||
double estimateExcessKurtosis(Builder builder) {
|
||||
return (builder.n - 1.0) / (builder.n - 2.0) / (builder.n - 3.0)
|
||||
* ((builder.n + 1.0) * (builder.n * builder.m4 / (builder.m2 * builder.m2) - 3.0)
|
||||
+ 6.0);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Instructs the builder to compute statistics assuming the sequence of doubles is the entire
|
||||
* population. The values computed here are usually the standard definitions of the statistical
|
||||
* quantities.
|
||||
*/
|
||||
POPULATION {
|
||||
@Override
|
||||
double estimateVariance(Builder builder) {
|
||||
return builder.m2 / builder.n;
|
||||
}
|
||||
|
||||
@Override
|
||||
double estimateSkewness(Builder builder) {
|
||||
return sqrt(builder.n) * builder.m3 / pow(builder.m2, 1.5);
|
||||
}
|
||||
|
||||
@Override
|
||||
double estimateExcessKurtosis(Builder builder) {
|
||||
return builder.n * builder.m4 / (builder.m2 * builder.m2) - 3;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Estimate the variance of the sequence
|
||||
*
|
||||
* @param builder the builder from which to estimate
|
||||
*
|
||||
* @return the variance of the sample
|
||||
*/
|
||||
abstract double estimateVariance(Builder builder);
|
||||
|
||||
/**
|
||||
* Estimate the skewness of the sequence
|
||||
*
|
||||
* @param builder the builder from which to estimate
|
||||
*
|
||||
* @return the skewness of the sample
|
||||
*/
|
||||
abstract double estimateSkewness(Builder builder);
|
||||
|
||||
/**
|
||||
* Estimate the excess (above normal) kurtosis of the sequence
|
||||
*
|
||||
* @param builder the builder from which to estimate
|
||||
*
|
||||
* @return the excess kurtosis
|
||||
*/
|
||||
abstract double estimateExcessKurtosis(Builder builder);
|
||||
}
|
||||
|
||||
private final double mean;
|
||||
private final double variance;
|
||||
private final double skewness;
|
||||
private final double excessKurtosis;
|
||||
private final double maximumValue;
|
||||
private final double minimumValue;
|
||||
private final int samples;
|
||||
private final ImmutableList<Integer> minimumIndices;
|
||||
private final ImmutableList<Integer> maximumIndices;
|
||||
private final Tracker tracker;
|
||||
private final Estimator estimator;
|
||||
|
||||
/**
|
||||
* Values preserved purely for re-populating a builder.
|
||||
*/
|
||||
private final double m2;
|
||||
private final double m3;
|
||||
private final double m4;
|
||||
|
||||
/**
|
||||
* Creates a new instance of Statistics
|
||||
*
|
||||
* @param samples the number of samples in the set
|
||||
* @param mean the mean of the sequence
|
||||
* @param variance the variance of the sequence
|
||||
* @param skewness the skewness of the sequence
|
||||
* @param excessKurtosis the excess (above normality) kurtosis
|
||||
* @param maximumValue the maximum value achieved by the sequence
|
||||
* @param minimumValue the minimum value achieved by the sequence
|
||||
* @param maximumIndices the tracked indices of the maximum values (may be empty)
|
||||
* @param minimumIndices the tracked indices of the minimum values (may be empty)
|
||||
* @param m2 the second central moment of the sequence
|
||||
* @param m3 the third central moment of the sequence
|
||||
* @param m4 the fourth central moment of the sequence
|
||||
* @param tracker the tracker used to capture extrema indices
|
||||
* @param estimator the estimator used to compute higher order statistics
|
||||
*/
|
||||
private Statistics(int samples, double mean, double variance, double skewness,
|
||||
double excessKurtosis, double maximumValue, double minimumValue,
|
||||
ImmutableList<Integer> maximumIndices, ImmutableList<Integer> minimumIndices, double m2,
|
||||
double m3, double m4, Tracker tracker, Estimator estimator) {
|
||||
super();
|
||||
this.mean = mean;
|
||||
this.variance = variance;
|
||||
this.skewness = skewness;
|
||||
this.excessKurtosis = excessKurtosis;
|
||||
this.maximumValue = maximumValue;
|
||||
this.minimumValue = minimumValue;
|
||||
this.samples = samples;
|
||||
this.minimumIndices = minimumIndices;
|
||||
this.maximumIndices = maximumIndices;
|
||||
this.m2 = m2;
|
||||
this.m3 = m3;
|
||||
this.m4 = m4;
|
||||
this.tracker = tracker;
|
||||
this.estimator = estimator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the sum of the sequence over which the statistics were computed.
|
||||
*
|
||||
* @return the sum of the sequence
|
||||
*/
|
||||
public double getSum() {
|
||||
return mean * samples;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the mean of the sequence over which the statistics were computed.
|
||||
*
|
||||
* @return the mean of the sequence
|
||||
*/
|
||||
public double getMean() {
|
||||
return mean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the variance of the sequence over which the statistics were computed.
|
||||
* <p>
|
||||
* The value stored here is impacted by the estimator utilized to build the statistics. See
|
||||
* {@link Estimator} for details.
|
||||
* </p>
|
||||
*
|
||||
* @return the variance of the sequence per {@link Statistics#getEstimator()}
|
||||
*/
|
||||
public double getVariance() {
|
||||
return variance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the standard deviation of the sequence over which the statistics were computed.
|
||||
* <p>
|
||||
* The value stored here is impacted by the estimator utilized to build the statistics. See
|
||||
* {@link Estimator} for details.
|
||||
* </p>
|
||||
*
|
||||
* @return the standard deviation of the sequence per {@link Statistics#getEstimator()}
|
||||
*/
|
||||
public double getStandardDeviation() {
|
||||
return sqrt(variance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the skewness of the sequence over which the statistics were computed.
|
||||
* <p>
|
||||
* The value stored here is impacted by the estimator utilized to build the statistics. See
|
||||
* {@link Estimator} for details.
|
||||
* </p>
|
||||
*
|
||||
* @return the skewness of the sequence per {@link Statistics#getEstimator()}
|
||||
*/
|
||||
public double getSkewness() {
|
||||
return skewness;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the excess kurtosis of the sequence over which the statistics were computed.
|
||||
* <p>
|
||||
* The value stored here is impacted by the estimator utilized to build the statistics. See
|
||||
* {@link Estimator} for details.
|
||||
* </p>
|
||||
* <p>
|
||||
* The kurtosis of a normal distribution is 3, so this value here subtracts 3 from it to yield an
|
||||
* excess kurtosis of a normal distribution as 0.
|
||||
* </p>
|
||||
*
|
||||
* @return the excess kurtosis of the sequence per {@link Statistics#getEstimator()}
|
||||
*/
|
||||
public double getExcessKurtosis() {
|
||||
return excessKurtosis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the maximum value achieved in the sequence over which the statistics were computed.
|
||||
*
|
||||
* @return the maximum value
|
||||
*/
|
||||
public double getMaximumValue() {
|
||||
return maximumValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the minimum value achieved in the sequence over which the statistics were computed.
|
||||
*
|
||||
* @return the minimum value
|
||||
*/
|
||||
public double getMinimumValue() {
|
||||
return minimumValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the number of samples used to estimate the statistics.
|
||||
*
|
||||
* @return the number of samples
|
||||
*/
|
||||
public int getSamples() {
|
||||
return samples;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an immutable list of integer indices into the total accumulated sequence used to
|
||||
* generate the statistics at which the minimum value occurred.
|
||||
* <p>
|
||||
* Depending on the index tracker utilized, this list may be empty, contain all of the indices at
|
||||
* which minimums occurred, or a subset of these indices. If multiple iterators or iterables were
|
||||
* used to accumulate the statistics, then the indices presented here start at 0 with the first
|
||||
* value supplied to the builder in the accumulation process and increase monotonically.
|
||||
* </p>
|
||||
*
|
||||
* @return the list of indices
|
||||
*/
|
||||
public ImmutableList<Integer> getMinimumIndices() {
|
||||
return minimumIndices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an immutable list of integer indices into the total accumulated sequence used to
|
||||
* generate the statistics at which the maximum value occurred.
|
||||
* <p>
|
||||
* Depending on the index tracker utilized, this list may be empty, contain all of the indices at
|
||||
* which maximums occurred, or a subset of these indices. If multiple iterators or iterables were
|
||||
* used to accumulate the statistics, then the indices presented here start at 0 with the first
|
||||
* value supplied to the builder in the accumulation process and increase monotonically.
|
||||
* </p>
|
||||
*
|
||||
* @return the list of indices
|
||||
*/
|
||||
public ImmutableList<Integer> getMaximumIndices() {
|
||||
return maximumIndices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the index tracker enumeration used to build the minimum and maximum indices lists
|
||||
*
|
||||
* @return the tracker
|
||||
*/
|
||||
public Tracker getTracker() {
|
||||
return tracker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the estimator enumeration used to compute the higher order statistics captured in the
|
||||
* instance
|
||||
*
|
||||
* @return the estimator
|
||||
*/
|
||||
public Estimator getEstimator() {
|
||||
return estimator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Statistics [samples=" + samples + ", estimator=" + estimator + ", mean=" + mean
|
||||
+ ", variance=" + variance + ", skewness=" + skewness + ", excessKurtosis=" + excessKurtosis
|
||||
+ ", maximumValue=" + maximumValue + ", minimumValue=" + minimumValue + ", tracker="
|
||||
+ tracker + ", minimumIndices=" + minimumIndices + ", maximumIndices=" + maximumIndices
|
||||
+ "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((estimator == null) ? 0 : estimator.hashCode());
|
||||
long temp;
|
||||
temp = Double.doubleToLongBits(excessKurtosis);
|
||||
result = prime * result + (int) (temp ^ (temp >>> 32));
|
||||
result = prime * result + ((maximumIndices == null) ? 0 : maximumIndices.hashCode());
|
||||
temp = Double.doubleToLongBits(maximumValue);
|
||||
result = prime * result + (int) (temp ^ (temp >>> 32));
|
||||
temp = Double.doubleToLongBits(mean);
|
||||
result = prime * result + (int) (temp ^ (temp >>> 32));
|
||||
result = prime * result + ((minimumIndices == null) ? 0 : minimumIndices.hashCode());
|
||||
temp = Double.doubleToLongBits(minimumValue);
|
||||
result = prime * result + (int) (temp ^ (temp >>> 32));
|
||||
result = prime * result + samples;
|
||||
temp = Double.doubleToLongBits(skewness);
|
||||
result = prime * result + (int) (temp ^ (temp >>> 32));
|
||||
result = prime * result + ((tracker == null) ? 0 : tracker.hashCode());
|
||||
temp = Double.doubleToLongBits(variance);
|
||||
result = prime * result + (int) (temp ^ (temp >>> 32));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Statistics other = (Statistics) obj;
|
||||
if (estimator != other.estimator) {
|
||||
return false;
|
||||
}
|
||||
if (Double.doubleToLongBits(excessKurtosis) != Double.doubleToLongBits(other.excessKurtosis)) {
|
||||
return false;
|
||||
}
|
||||
if (maximumIndices == null) {
|
||||
if (other.maximumIndices != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!maximumIndices.equals(other.maximumIndices)) {
|
||||
return false;
|
||||
}
|
||||
if (Double.doubleToLongBits(maximumValue) != Double.doubleToLongBits(other.maximumValue)) {
|
||||
return false;
|
||||
}
|
||||
if (Double.doubleToLongBits(mean) != Double.doubleToLongBits(other.mean)) {
|
||||
return false;
|
||||
}
|
||||
if (minimumIndices == null) {
|
||||
if (other.minimumIndices != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!minimumIndices.equals(other.minimumIndices)) {
|
||||
return false;
|
||||
}
|
||||
if (Double.doubleToLongBits(minimumValue) != Double.doubleToLongBits(other.minimumValue)) {
|
||||
return false;
|
||||
}
|
||||
if (samples != other.samples) {
|
||||
return false;
|
||||
}
|
||||
if (Double.doubleToLongBits(skewness) != Double.doubleToLongBits(other.skewness)) {
|
||||
return false;
|
||||
}
|
||||
if (tracker != other.tracker) {
|
||||
return false;
|
||||
}
|
||||
if (Double.doubleToLongBits(variance) != Double.doubleToLongBits(other.variance)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder that accumulates sequences of doubles and is capable of computing a statistical
|
||||
* snapshot via its build() method.
|
||||
* <p>
|
||||
* The builder has a few configuration options, namely specification of an index tracker
|
||||
* {@link Tracker} and a higher order estimator {@link Estimator}. The tracker must be supplied to
|
||||
* the constructor as it has a fundamental impact on the way the builder accumulates the indices
|
||||
* of the extrema, so this decision must be made at construction time. On the other hand, the
|
||||
* statistics estimation is performed just at build time, so it may be adjusted after the builder
|
||||
* is created. {@link Builder#Builder()} describes the default options for the builder.
|
||||
* </p>
|
||||
* <p>
|
||||
* If you are intending to accumulate statistics individually {@link Builder#accumulate(double)}
|
||||
* or via multiple invocations of {@link Builder#accumulate(Iterable)} or
|
||||
* {@link Builder#accumulate(Iterator)} the indices present in the Statistics class will be
|
||||
* referenced assuming the first element starts with index 0 and increases from there. So the
|
||||
* connection back to the indices of the various accumulation entry points is broken.
|
||||
* </p>
|
||||
*/
|
||||
public static final class Builder
|
||||
implements picante.designpatterns.Builder<Statistics, RuntimeException> {
|
||||
|
||||
/**
|
||||
* Creates the default builder. {@link Tracker#FIRST} is utilized to track indices, and
|
||||
* {@link Estimator#SAMPLE} is assumed initially.
|
||||
*/
|
||||
public Builder() {
|
||||
this.tracker = Tracker.FIRST;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder with the supplied tracker. {@link Estimator#SAMPLE} is assumed initially.
|
||||
*
|
||||
* @param tracker the tracker to utilize
|
||||
*/
|
||||
public Builder(Tracker tracker) {
|
||||
this.tracker = tracker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder from a previously accumulated statistic. The tracker utilized is precisely
|
||||
* the same as that used to create the supplied statistics. Further, the estimator is assigned
|
||||
* to the one used to produce the statistics, but may be changed.
|
||||
*
|
||||
* @param statistics
|
||||
*/
|
||||
public Builder(Statistics statistics) {
|
||||
this.n = statistics.samples;
|
||||
this.mean = statistics.mean;
|
||||
this.m2 = statistics.m2;
|
||||
this.m3 = statistics.m3;
|
||||
this.m4 = statistics.m4;
|
||||
this.min = statistics.minimumValue;
|
||||
this.max = statistics.maximumValue;
|
||||
this.minIndices.addAll(statistics.minimumIndices);
|
||||
this.maxIndices.addAll(statistics.maximumIndices);
|
||||
this.tracker = statistics.tracker;
|
||||
this.estimator = statistics.estimator;
|
||||
}
|
||||
|
||||
/**
|
||||
* The estimator, by default {@link Estimator#SAMPLE} is utilized
|
||||
*/
|
||||
private Estimator estimator = Estimator.SAMPLE;
|
||||
private final Tracker tracker;
|
||||
|
||||
private final LinkedList<Integer> maxIndices = Lists.newLinkedList();
|
||||
private final LinkedList<Integer> minIndices = Lists.newLinkedList();
|
||||
|
||||
private double max = -Double.MAX_VALUE;
|
||||
private double min = Double.MAX_VALUE;
|
||||
|
||||
/*
|
||||
* These fields are package level access to allow the estimator enumeration direct access to
|
||||
* them. (Yes, this is a bit lazy coding, but it's all contained internally in the package.)
|
||||
*/
|
||||
int n = 0;
|
||||
double mean = 0;
|
||||
double m2 = 0;
|
||||
double m3 = 0;
|
||||
double m4 = 0;
|
||||
|
||||
/**
|
||||
* Creates a new statistical snapshot of the accumulated sequence so far.
|
||||
*
|
||||
* @throws BuildFailedException if the builder has accumulated no data
|
||||
*/
|
||||
@Override
|
||||
public Statistics build() {
|
||||
if (n == 0) {
|
||||
throw new BuildFailedException("Unable to build statistics, no samples provided.");
|
||||
}
|
||||
|
||||
return new Statistics(n, mean, n < 2 ? 0 : estimator.estimateVariance(this),
|
||||
n < 3 ? 0 : estimator.estimateSkewness(this),
|
||||
n < 4 ? 0 : estimator.estimateExcessKurtosis(this), max, min,
|
||||
ImmutableList.copyOf(maxIndices), ImmutableList.copyOf(minIndices), m2, m3, m4, tracker,
|
||||
estimator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Accumulate an additional data element
|
||||
*
|
||||
* @param x the element to add into the sequence
|
||||
*
|
||||
* @return a reference to the builder instance for chaining convenience
|
||||
*/
|
||||
public Builder accumulate(double x) {
|
||||
|
||||
int n1 = n;
|
||||
n++;
|
||||
double delta = x - mean;
|
||||
double delta_n = delta / n;
|
||||
double delta_n2 = delta_n * delta_n;
|
||||
double term1 = delta * delta_n * n1;
|
||||
mean += delta_n;
|
||||
m4 += term1 * delta_n2 * (n * n - 3 * n + 3) + 6 * delta_n2 * m2 - 4 * delta_n * m3;
|
||||
m3 += term1 * delta_n * (n - 2) - 3 * delta_n * m2;
|
||||
m2 += term1;
|
||||
|
||||
if (x >= max) {
|
||||
tracker.record(maxIndices, max, x, n1);
|
||||
max = x;
|
||||
}
|
||||
|
||||
if (x <= min) {
|
||||
tracker.record(minIndices, min, x, n1);
|
||||
min = x;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accumulate all of the elements of a supplied iterable
|
||||
*
|
||||
* @param data the iterable from which to accumulate
|
||||
*
|
||||
* @return a reference to the builder instance for chaining convenience
|
||||
*/
|
||||
public Builder accumulate(Iterable<Double> data) {
|
||||
|
||||
for (double d : data) {
|
||||
accumulate(d);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accumulate all of the elements of a supplied iterator, consuming it
|
||||
*
|
||||
* @param data the iterator which to consume and from which to accumulate
|
||||
*
|
||||
* @return a reference to the builder instance for chaining convenience
|
||||
*/
|
||||
public Builder accumulate(Iterator<Double> data) {
|
||||
|
||||
while (data.hasNext()) {
|
||||
accumulate(data.next());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the index tracker specified at construction of the builder
|
||||
*
|
||||
* @return the tracker utilized by the builder
|
||||
*/
|
||||
public Tracker getTracker() {
|
||||
return tracker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates the builder should utilize the supplied estimator for subsequent build operations
|
||||
*
|
||||
* @param estimator the estimator to utilize
|
||||
*
|
||||
* @return a reference to the builder instance for chaining convenience
|
||||
*/
|
||||
public Builder useEstimator(Estimator estimator) {
|
||||
this.estimator = estimator;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the estimator currently in use by the builder
|
||||
*
|
||||
* @return the estimator
|
||||
*/
|
||||
public Estimator getEstimator() {
|
||||
return this.estimator;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a statistics builder.
|
||||
*
|
||||
* @return newly created, default configured Statistics.Builder instance
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a statistics instance from the supplied iterable assuming the {@link Estimator#SAMPLE}
|
||||
* estimator and {@link Tracker#FIRST} index tracker.
|
||||
*
|
||||
* @param data the iterable of data to compute statistics
|
||||
*
|
||||
* @return a newly created statistics instance
|
||||
*/
|
||||
public static Statistics createSampleStatistics(Iterable<Double> data) {
|
||||
return new Builder().useEstimator(Estimator.SAMPLE).accumulate(data).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a statistics instance from the supplied iterable assuming the
|
||||
* {@link Estimator#POPULATION} estimator and {@link Tracker#FIRST} index tracker.
|
||||
*
|
||||
* @param data the iterable of data to compute statistics
|
||||
*
|
||||
* @return a newly created statistics instance
|
||||
*/
|
||||
public static Statistics createPopulationStatistics(Iterable<Double> data) {
|
||||
return new Builder().useEstimator(Estimator.POPULATION).accumulate(data).build();
|
||||
}
|
||||
|
||||
}
|
||||
26
src/main/java/picante/math/cones/AbstractProjection.java
Normal file
26
src/main/java/picante/math/cones/AbstractProjection.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package picante.math.cones;
|
||||
|
||||
import picante.math.vectorspace.UnwritableRotationMatrixIJK;
|
||||
|
||||
/**
|
||||
* Basic abstract class that implements the derived new shape projection methods for all
|
||||
* implementations to utilize.
|
||||
*
|
||||
* @author J.E.Turner
|
||||
*/
|
||||
abstract class AbstractProjection implements Projection {
|
||||
|
||||
@Override
|
||||
public Class<? extends Projection> getOriginalClass() {
|
||||
return getClass();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Projection rotate(UnwritableRotationMatrixIJK rotation) {
|
||||
return new RotatedProjection(this, rotation);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
58
src/main/java/picante/math/cones/ClosedPathPlotter.java
Normal file
58
src/main/java/picante/math/cones/ClosedPathPlotter.java
Normal file
@@ -0,0 +1,58 @@
|
||||
package picante.math.cones;
|
||||
|
||||
import java.awt.geom.Point2D;
|
||||
import picante.math.intervals.UnwritableInterval;
|
||||
|
||||
/**
|
||||
* Interface describing a closed parameterized path in 2D.
|
||||
*/
|
||||
interface ClosedPathPlotter extends PathPlotter {
|
||||
|
||||
/**
|
||||
*
|
||||
* @return a boolean indicating if the reference point is on the interior of the closed path
|
||||
* represented by this plotter
|
||||
*/
|
||||
boolean isReferencePointInterior();
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves a point either in the exterior or the interior of the closed path represented by this
|
||||
* plotter.
|
||||
*
|
||||
*/
|
||||
Point2D getReferencePoint(Point2D buffer);
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves a point either in the exterior or the interior of the closed path represented by this
|
||||
* plotter.
|
||||
*
|
||||
* @param buffer a buffer to receive the result
|
||||
*
|
||||
* @return a reference to the supplied buffer for conveninece of method chaining
|
||||
*/
|
||||
default Point2D getReferencePoint() {
|
||||
return getReferencePoint(new Point2D.Double());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>
|
||||
* Since the path is closed, the following should be true in general:
|
||||
*
|
||||
* <pre>
|
||||
* plot(getDomain().getBegin(), new Point2D.Double()).equals(plot(getDomain().getEnd(), new Point2D.Double())
|
||||
* </pre>
|
||||
*
|
||||
* which basically asserts that the plotted point at the beginning and end of the domain is the
|
||||
* same.
|
||||
* </p>
|
||||
*/
|
||||
@Override
|
||||
UnwritableInterval getDomain();
|
||||
|
||||
|
||||
|
||||
}
|
||||
52
src/main/java/picante/math/cones/Cone.java
Normal file
52
src/main/java/picante/math/cones/Cone.java
Normal file
@@ -0,0 +1,52 @@
|
||||
package picante.math.cones;
|
||||
|
||||
import picante.math.intervals.UnwritableInterval;
|
||||
import picante.math.vectorspace.UnwritableVectorIJK;
|
||||
import picante.math.vectorspace.VectorIJK;
|
||||
|
||||
/**
|
||||
* Interface parameterizing a cone in three dimensions.
|
||||
* <p>
|
||||
* In our current plan, these cones should either be convex or the complement of a convex cone.
|
||||
* Algorithms for manipulating implementations of this interface may implicitly assume that is the
|
||||
* case.
|
||||
* </p>
|
||||
*
|
||||
* @author C.M. O'Shea
|
||||
* @author R.T. Poffenbarger
|
||||
*
|
||||
*/
|
||||
public interface Cone {
|
||||
|
||||
/**
|
||||
* Retrieves the vector from the origin of the coordinate system to the cone's vertex.
|
||||
*
|
||||
* @return a vector, may be {@link VectorIJK#ZERO}
|
||||
*/
|
||||
UnwritableVectorIJK getVertex();
|
||||
|
||||
/**
|
||||
* Retrieves a vector along the edge of the cone.
|
||||
*
|
||||
* @param parameter the value of the parameter specifying which edge vector to select.
|
||||
*
|
||||
* @return a vector, of non-zero length, that points from the vertex along the edge of the cone at
|
||||
* parameter p
|
||||
*/
|
||||
UnwritableVectorIJK getEdge(double parameter);
|
||||
|
||||
/**
|
||||
* Retrieves the domain of the parameter p.
|
||||
*
|
||||
* @return an interval, where {@link UnwritableInterval#getBegin()} and
|
||||
* {@link UnwritableInterval#getEnd()} map to the same edge vector along the cone.
|
||||
*/
|
||||
UnwritableInterval getParameterDomain();
|
||||
|
||||
/**
|
||||
* Retrieves a vector in the interior of the cone.
|
||||
*
|
||||
* @return the vector
|
||||
*/
|
||||
UnwritableVectorIJK getInteriorPoint();
|
||||
}
|
||||
24
src/main/java/picante/math/cones/ConeFunction.java
Normal file
24
src/main/java/picante/math/cones/ConeFunction.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package picante.math.cones;
|
||||
|
||||
/**
|
||||
* A univariate function that returns a cone.
|
||||
* <p>
|
||||
* The parameter of the function would usually be time, to capture the evolution of a cone in time.
|
||||
* </p>
|
||||
*
|
||||
* @author C.M. O'Shea
|
||||
* @author R.T. Poffenbarger
|
||||
*
|
||||
*/
|
||||
public interface ConeFunction {
|
||||
|
||||
/**
|
||||
* Returns a cone as evaluated at the specified parameter t.
|
||||
*
|
||||
* @param t the parameter
|
||||
*
|
||||
* @return the cone
|
||||
*/
|
||||
Cone evaluate(double t);
|
||||
|
||||
}
|
||||
810
src/main/java/picante/math/cones/Cones.java
Normal file
810
src/main/java/picante/math/cones/Cones.java
Normal file
@@ -0,0 +1,810 @@
|
||||
package picante.math.cones;
|
||||
|
||||
import java.awt.geom.Path2D;
|
||||
import java.awt.geom.PathIterator;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import org.locationtech.jts.geom.Coordinate;
|
||||
import org.locationtech.jts.geom.Geometry;
|
||||
import org.locationtech.jts.geom.MultiPolygon;
|
||||
import org.locationtech.jts.geom.Point;
|
||||
import org.locationtech.jts.operation.union.CascadedPolygonUnion;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import picante.math.coords.CoordConverters;
|
||||
import picante.math.coords.LatitudinalVector;
|
||||
import picante.math.intervals.UnwritableInterval;
|
||||
import picante.math.vectorspace.RotationMatrixIJK;
|
||||
import picante.math.vectorspace.UnwritableVectorIJK;
|
||||
import picante.math.vectorspace.VectorIJK;
|
||||
import picante.surfaces.Ellipse;
|
||||
import picante.surfaces.Ellipsoid;
|
||||
import picante.surfaces.NoIntersectionException;
|
||||
import picante.surfaces.Plane;
|
||||
|
||||
/**
|
||||
* Static utility method collection for manipulating and working with {@link Cone}s and
|
||||
* {@link ConeFunction}s
|
||||
*
|
||||
* @author C.M. O'Shea
|
||||
* @author R.T. Poffenbarger
|
||||
*
|
||||
*/
|
||||
public class Cones {
|
||||
|
||||
/**
|
||||
* Block construction of the class, there's no need for instances of it to exist.
|
||||
*/
|
||||
private Cones() {}
|
||||
|
||||
/**
|
||||
* Creates a constant {@link ConeFunction}
|
||||
*
|
||||
* @param cone the constant value
|
||||
*
|
||||
* @return a {@link ConeFunction} that returns the supplied cone for any provided input
|
||||
*/
|
||||
public static ConeFunction createConstant(Cone cone) {
|
||||
return (t) -> cone;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a {@link ClosedPathPlotter} from a cone with a vertex at the center of the map
|
||||
* projected body.
|
||||
*
|
||||
* @param cone the cone to plot
|
||||
*
|
||||
* @return the closed path plotter
|
||||
*/
|
||||
static ClosedPathPlotter plot(Cone cone) {
|
||||
return new ClosedPathPlotter() {
|
||||
@Override
|
||||
public Point2D plot(double p, Point2D buffer) {
|
||||
return PathPlotters.convert(cone.getEdge(p), buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReferencePointInterior() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point2D getReferencePoint(Point2D buffer) {
|
||||
return PathPlotters.convert(cone.getInteriorPoint(), buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnwritableInterval getDomain() {
|
||||
return cone.getParameterDomain();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link EllipticalCone}. The first generating vector of the ellipse is the projection
|
||||
* of the reference vector onto the plane normal to the boresight, scaled by the tangent of the
|
||||
* reference angle. The second generating vector is the unitized cross product of the first
|
||||
* generating vector and the boresight, scaled by the tangent or the cross angle.
|
||||
*
|
||||
* @param vertex cone vertex
|
||||
* @param boresight cone axis
|
||||
* @param refVector
|
||||
* @param refAngle
|
||||
* @param crossAngle
|
||||
* @return
|
||||
*/
|
||||
public static EllipticalCone createEllipticalCone(UnwritableVectorIJK vertex,
|
||||
UnwritableVectorIJK boresight, UnwritableVectorIJK refVector, double refAngle,
|
||||
double crossAngle) {
|
||||
|
||||
UnwritableVectorIJK interiorPoint = boresight.createUnitized();
|
||||
VectorIJK axis1 = VectorIJK.planeProject(refVector, interiorPoint);
|
||||
VectorIJK axis2 = VectorIJK.uCross(axis1, interiorPoint);
|
||||
axis1.unitize().scale(Math.tan(refAngle));
|
||||
axis2.unitize().scale(Math.tan(crossAngle));
|
||||
|
||||
return new EllipticalCone(vertex, interiorPoint, axis1, axis2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link EllipticalCone} from a vertex and Ellipse
|
||||
*/
|
||||
public static EllipticalCone createEllipticalCone(UnwritableVectorIJK bodyToConeVertex,
|
||||
Ellipse limb) {
|
||||
UnwritableVectorIJK semiMajor = new VectorIJK(limb.getSemiMajorAxis());
|
||||
UnwritableVectorIJK semiMinor = new VectorIJK(limb.getSemiMinorAxis());
|
||||
return new EllipticalCone(bodyToConeVertex, limb.getCenter(), semiMajor, semiMinor);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a {@link PolygonalCone}
|
||||
*/
|
||||
public static PolygonalCone createPolygonalCone(UnwritableVectorIJK vertex,
|
||||
UnwritableVectorIJK interiorPt, List<UnwritableVectorIJK> pts3d) {
|
||||
return new PolygonalCone(vertex, interiorPt, pts3d);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link PolygonalCone} with four sides. Follows the logic in the NAIF routine GETFOV:
|
||||
*
|
||||
* <pre>
|
||||
(1) Normalize BSIGHT, label it B.
|
||||
|
||||
(2) Compute the unit vector in the plane defined by REFVEC
|
||||
and B that is normal to B and pointing towards
|
||||
REFVEC, label this B1.
|
||||
|
||||
(3) Cross B and B1 to obtain B2. These three vectors
|
||||
form a basis that is 'aligned' with the FOV cone.
|
||||
|
||||
(4) Compute the inward normals to the sides of the
|
||||
rectangular cone in a counter-clockwise order
|
||||
about the boresight:
|
||||
|
||||
NORMAL(1) = -COS(REFANG)*B1 + SIN(REFANG)*B
|
||||
NORMAL(2) = -COS(CRSANG)*B2 + SIN(CRSANG)*B
|
||||
NORMAL(3) = COS(REFANG)*B1 + SIN(REFANG)*B
|
||||
NORMAL(4) = COS(CRSANG)*B2 + SIN(CRSANG)*B
|
||||
|
||||
(5) Compute the appropriate cross products to obtain
|
||||
a set of boundary corner vectors:
|
||||
|
||||
BOUNDS(1) = NORMAL(1) x NORMAL(2)
|
||||
BOUNDS(2) = NORMAL(2) x NORMAL(3)
|
||||
BOUNDS(3) = NORMAL(3) x NORMAL(4)
|
||||
BOUNDS(4) = NORMAL(4) x NORMAL(1)
|
||||
|
||||
(6) Unitize BOUNDS.
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
* @param vertex
|
||||
* @param boresight axis of the cone
|
||||
* @param refVector together with the boresight vector defines the plane in which refAngle is
|
||||
* measured
|
||||
* @param refAngle 1/2 of the total angular extent of the cone in the plane defined by the
|
||||
* boresight and refVector
|
||||
* @param crossAngle 1/2 of the total angular extent of the cone in the plane defined by the
|
||||
* boresight and perpendicular to the refAngle plane
|
||||
* @return
|
||||
*/
|
||||
public static PolygonalCone createRectangularCone(UnwritableVectorIJK vertex,
|
||||
UnwritableVectorIJK boresight, UnwritableVectorIJK refVector, double refAngle,
|
||||
double crossAngle) {
|
||||
|
||||
UnwritableVectorIJK b = boresight.createUnitized();
|
||||
Plane p = new Plane(b, vertex);
|
||||
VectorIJK b1 = p.projectOnto(refVector).unitize();
|
||||
VectorIJK b2 = VectorIJK.cross(b, b1);
|
||||
|
||||
double cosRef = Math.cos(refAngle);
|
||||
double sinRef = Math.sin(refAngle);
|
||||
double cosCrs = Math.cos(crossAngle);
|
||||
double sinCrs = Math.sin(crossAngle);
|
||||
|
||||
VectorIJK normal1 = VectorIJK.combine(-cosRef, b1, sinRef, b);
|
||||
VectorIJK normal2 = VectorIJK.combine(-cosCrs, b2, sinCrs, b);
|
||||
VectorIJK normal3 = VectorIJK.combine(cosRef, b1, sinRef, b);
|
||||
VectorIJK normal4 = VectorIJK.combine(cosCrs, b2, sinCrs, b);
|
||||
|
||||
List<UnwritableVectorIJK> bounds = new ArrayList<>();
|
||||
bounds.add(VectorIJK.uCross(normal1, normal2));
|
||||
bounds.add(VectorIJK.uCross(normal2, normal3));
|
||||
bounds.add(VectorIJK.uCross(normal3, normal4));
|
||||
bounds.add(VectorIJK.uCross(normal4, normal1));
|
||||
|
||||
return new PolygonalCone(vertex, boresight, bounds);
|
||||
}
|
||||
|
||||
|
||||
public static Optional<Cone> union(Cone cone1, double step1, Cone cone2, double step2) {
|
||||
/*
|
||||
* Make sure cones have vertex in the same location
|
||||
*/
|
||||
Preconditions.checkArgument(cone1.getVertex().equals(cone2.getVertex()),
|
||||
"The cones to be intersected must have the same vertex");
|
||||
|
||||
/*
|
||||
* project onto the rotated shape projection with X axis = interior point of cone 1 and Z axis =
|
||||
* either J or K and create Path2Ds from the cones
|
||||
*/
|
||||
Projection projection = createRotatedProjectionFromCone(cone1);
|
||||
Path2D.Double cone1path = getConeAsPath(cone1, step1, projection);
|
||||
Path2D.Double cone2path = getConeAsPath(cone2, step2, projection);
|
||||
|
||||
/*
|
||||
* convert to JTS Shapes and find union
|
||||
*/
|
||||
Function<PathIterator, Geometry> pathToGeometryConverter =
|
||||
JtsUtilities.pathIteratorToGeometryConverter();
|
||||
Geometry jtsShape1 = pathToGeometryConverter.apply(cone1path.getPathIterator(null)).buffer(0);
|
||||
Geometry jtsShape2 = pathToGeometryConverter.apply(cone2path.getPathIterator(null)).buffer(0);
|
||||
Geometry union = jtsShape1.union(jtsShape2);
|
||||
|
||||
return Cones.createFromGeometry(union, projection, cone1.getVertex());
|
||||
|
||||
}
|
||||
|
||||
public static Optional<Cone> intersect(Cone cone1, double step1, Cone cone2, double step2) {
|
||||
/*
|
||||
* Make sure cones have vertex in the same location
|
||||
*/
|
||||
Preconditions.checkArgument(cone1.getVertex().equals(cone2.getVertex()),
|
||||
"The cones to be intersected must have the same vertex");
|
||||
|
||||
/*
|
||||
* project onto the rotated shape projection with X axis = interior point of cone 1 and Z axis =
|
||||
* either J or K and create Path2Ds from the cones
|
||||
*/
|
||||
Projection projection = createRotatedProjectionFromCone(cone1);
|
||||
Path2D.Double cone1path = getConeAsPath(cone1, step1, projection);
|
||||
Path2D.Double cone2path = getConeAsPath(cone2, step2, projection);
|
||||
|
||||
/*
|
||||
* convert to JTS Shapes and find intersection
|
||||
*/
|
||||
Function<PathIterator, Geometry> pathToGeometryConverter =
|
||||
JtsUtilities.pathIteratorToGeometryConverter();
|
||||
Geometry jtsShape1 = pathToGeometryConverter.apply(cone1path.getPathIterator(null)).buffer(0);
|
||||
Geometry jtsShape2 = pathToGeometryConverter.apply(cone2path.getPathIterator(null)).buffer(0);
|
||||
Geometry intersection = jtsShape1.intersection(jtsShape2);
|
||||
|
||||
return Cones.createFromGeometry(intersection, projection, cone1.getVertex());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtract cone2 from cone1. Note, the resulting cone will likely not be convex and therefore
|
||||
* several methods in these interfaces may not work as expected. If there is no resulting cone,
|
||||
* this will return an empty list. There can also be multiple cones returned (image a cone
|
||||
* splitting another one right down the middle)
|
||||
*
|
||||
* @param cone1 the original cone
|
||||
* @param step1 the step size for cone1
|
||||
* @param cone2 the cone to subtract from the original cone
|
||||
* @param step2 the step size for cone2
|
||||
* @return list of cones resulting from the subtraction
|
||||
*/
|
||||
public static List<Cone> subtract(Cone cone1, double step1, Cone cone2, double step2) {
|
||||
List<Cone> cones = Lists.newArrayList();
|
||||
|
||||
/*
|
||||
* Make sure cones have vertex in the same location
|
||||
*/
|
||||
Preconditions.checkArgument(cone1.getVertex().equals(cone2.getVertex()),
|
||||
"The cones to be subtracted must have the same vertex");
|
||||
|
||||
Projection projection = createRotatedProjectionFromCone(cone1);
|
||||
Path2D.Double cone1path = getConeAsPath(cone1, step1, projection);
|
||||
Path2D.Double cone2path = getConeAsPath(cone2, step2, projection);
|
||||
|
||||
/*
|
||||
* convert to JTS Shapes and find intersection
|
||||
*/
|
||||
Function<PathIterator, Geometry> pathToGeometryConverter =
|
||||
JtsUtilities.pathIteratorToGeometryConverter();
|
||||
Geometry jtsShape1 = pathToGeometryConverter.apply(cone1path.getPathIterator(null)).buffer(0);
|
||||
Geometry jtsShape2 = pathToGeometryConverter.apply(cone2path.getPathIterator(null)).buffer(0);
|
||||
Geometry subtraction = jtsShape1.difference(jtsShape2);
|
||||
|
||||
/*
|
||||
* the result here could be multiple shapes
|
||||
*/
|
||||
if (subtraction instanceof MultiPolygon) {
|
||||
Geometry geom;
|
||||
Optional<Cone> cone;
|
||||
|
||||
int numGeoms = subtraction.getNumGeometries();
|
||||
for (int n = 0; n < numGeoms; n++) {
|
||||
geom = subtraction.getGeometryN(n);
|
||||
cone = Cones.createFromGeometry(geom, projection, cone1.getVertex());
|
||||
if (cone.isPresent()) {
|
||||
cones.add(cone.get());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* just return the Cone made from this geometry
|
||||
*/
|
||||
Optional<Cone> subtractionCone =
|
||||
Cones.createFromGeometry(subtraction, projection, cone1.getVertex());
|
||||
if (subtractionCone.isPresent()) {
|
||||
cones.add(subtractionCone.get());
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Create a cone that represents this intersection
|
||||
*/
|
||||
return cones;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Take a JTS Geometry, find the edge vectors and interior point and create a cone in 3D
|
||||
*
|
||||
* @param geometry
|
||||
* @param projection
|
||||
* @param vertex
|
||||
* @return
|
||||
*/
|
||||
private static Optional<Cone> createFromGeometry(Geometry geometry, Projection projection,
|
||||
UnwritableVectorIJK vertex) {
|
||||
|
||||
// TODO pass the tolerance as input
|
||||
if (!geometry.isEmpty() && !(geometry.getArea() < 1E-14)) {
|
||||
List<UnwritableVectorIJK> pts3D = getListOfPointsFromJTS(geometry, projection);
|
||||
|
||||
// find an interior point
|
||||
Point interiorPt = geometry.getInteriorPoint();
|
||||
Point2D.Double interiorPtDouble = new Point2D.Double(interiorPt.getX(), interiorPt.getY());
|
||||
|
||||
// normalized -> latlon -> 3D
|
||||
Point2D.Double latlon = projection.invert(interiorPtDouble, new Point2D.Double());
|
||||
UnwritableVectorIJK cartesianIntPoint =
|
||||
CoordConverters.convert(new LatitudinalVector(1, latlon.getY(), latlon.getX()));
|
||||
|
||||
PolygonalCone cone = Cones.createPolygonalCone(vertex, cartesianIntPoint, pts3D);
|
||||
return Optional.of(cone);
|
||||
} else {
|
||||
// there is no intersection
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Intersect and Subtract any number of cones to form a new cone. Intersect each in the list of
|
||||
* toIntersect, then subtract each cone in the list of toSubtract. Since subtracting can result in
|
||||
* a list, this method must return a list of Cones.
|
||||
*
|
||||
* @param toIntersect the list of cones included in the intersection
|
||||
* @param toSubtract the list of cones included in the subtraction
|
||||
* @param stepIntersect the list of steps to use for the toIntersect list of cones
|
||||
* @param stepSubtract the list of steps to use for the toSubtract list of cones
|
||||
* @return a list of resulting cones
|
||||
*/
|
||||
public static List<Cone> intersectAndSubtract(List<Cone> toIntersect, List<Double> stepIntersect,
|
||||
List<Cone> toSubtract, List<Double> stepSubtract) {
|
||||
List<Cone> cones = Lists.newArrayList();
|
||||
|
||||
Preconditions.checkArgument(stepIntersect.size() == toIntersect.size(),
|
||||
"The number of step sizes does not match the number of cones provided for the intersection");
|
||||
Preconditions.checkArgument(stepSubtract.size() == toSubtract.size(),
|
||||
"The number of step sizes does not match the number of cones provided for the subtraction");
|
||||
|
||||
if (toIntersect.size() == 0) {
|
||||
return cones;
|
||||
}
|
||||
|
||||
Optional<Cone> intersection = Optional.of(toIntersect.get(0));
|
||||
double step1 = stepIntersect.get(0);
|
||||
if (toIntersect.size() > 1) {
|
||||
for (int iCone = 1; iCone < toIntersect.size(); iCone++) {
|
||||
if (intersection.isPresent()) {
|
||||
intersection = Cones.intersect(intersection.get(), step1, toIntersect.get(iCone),
|
||||
stepIntersect.get(iCone));
|
||||
step1 = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* if nothing left after intersection, return empty list.
|
||||
*/
|
||||
if (!intersection.isPresent()) {
|
||||
return cones;
|
||||
}
|
||||
|
||||
|
||||
if (toSubtract.isEmpty()) {
|
||||
/*
|
||||
* nothing to subtract, return intersection
|
||||
*/
|
||||
cones.add(intersection.get());
|
||||
return cones;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* now we have the intersection and want to subtract the rest.
|
||||
*/
|
||||
List<Cone> previousSubtractionResult = Lists.newArrayList();
|
||||
List<Cone> currentSubtractionResult = Lists.newArrayList();
|
||||
|
||||
// subtract the first cone in the list
|
||||
previousSubtractionResult
|
||||
.addAll(Cones.subtract(intersection.get(), step1, toSubtract.get(0), stepSubtract.get(0)));
|
||||
/*
|
||||
* subtract the remaining from the resulting list
|
||||
*/
|
||||
for (int iCone = 1; iCone < toSubtract.size(); iCone++) {
|
||||
if (previousSubtractionResult.isEmpty()) {
|
||||
/*
|
||||
* return if no cones remain in list.
|
||||
*/
|
||||
return cones;
|
||||
}
|
||||
currentSubtractionResult.clear();
|
||||
for (Cone cone : previousSubtractionResult) {
|
||||
currentSubtractionResult
|
||||
.addAll(Cones.subtract(cone, step1, toSubtract.get(iCone), stepSubtract.get(iCone)));
|
||||
}
|
||||
previousSubtractionResult.clear();
|
||||
previousSubtractionResult.addAll(currentSubtractionResult);
|
||||
currentSubtractionResult.clear();
|
||||
step1 = 1;
|
||||
}
|
||||
|
||||
return previousSubtractionResult;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Intersect and Subtract any number of cones to form a new cone. Intersect each in the list of
|
||||
* toIntersect, then subtract each cone in the list of toSubtract. Use the step size stepIntersect
|
||||
* for each intersection path plotter and the step size stepSubtract for each subtraction path
|
||||
* plotter.
|
||||
*
|
||||
* @param toIntersect the list of cones included in the intersection
|
||||
* @param stepIntersect the step size for each of the cones in toIntersect
|
||||
* @param toSubtract the list of cones included in the subtraction
|
||||
* @param stepSubtract the step size for each of the ocnes in toSubtract
|
||||
* @return
|
||||
*/
|
||||
public static List<Cone> intersectAndSubtract(List<Cone> toIntersect, double stepIntersect,
|
||||
List<Cone> toSubtract, double stepSubtract) {
|
||||
List<Double> intersectSteps = Collections.nCopies(toIntersect.size(), stepIntersect);
|
||||
List<Double> subtractSteps = Collections.nCopies(toSubtract.size(), stepSubtract);
|
||||
return intersectAndSubtract(toIntersect, intersectSteps, toSubtract, subtractSteps);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Given a cone and a projection, create path plotters and project them into the given projection
|
||||
*
|
||||
* @param cone the cone to project
|
||||
* @param step the step used to project the path
|
||||
* @param projection the projection in which to project the path
|
||||
* @return
|
||||
*/
|
||||
private static Path2D.Double getConeAsPath(Cone cone, double step, Projection projection) {
|
||||
/*
|
||||
* create the path plotters
|
||||
*/
|
||||
ClosedPathPlotter conePlotter = Cones.plot(cone);
|
||||
|
||||
/*
|
||||
* create paths from path plotters in this projection
|
||||
*/
|
||||
Path2D.Double path = projection.projectClosedPath(conePlotter, step, new Path2D.Double());
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a geometry and projection, create a list of VectorIJK which are the cartesian points
|
||||
* around the boundary of the geometry
|
||||
*
|
||||
* @param intersection
|
||||
* @param projection
|
||||
* @return
|
||||
*/
|
||||
private static List<UnwritableVectorIJK> getListOfPointsFromJTS(Geometry intersection,
|
||||
Projection projection) {
|
||||
List<UnwritableVectorIJK> pts3D = Lists.newArrayList();
|
||||
/*
|
||||
* The area is in a simple cylindrical projection around the vertex. Want to invert back to 3D.
|
||||
* Loop through path iterator of the intersection - create list of Point2Ds
|
||||
*/
|
||||
List<Point2D.Double> points = Lists.newArrayList();
|
||||
Coordinate[] coords = intersection.getCoordinates();
|
||||
for (Coordinate coord : coords) {
|
||||
points.add(new Point2D.Double(coord.x, coord.y));
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert list of projected points to list of points in 3D. these are cartesian points around
|
||||
* the Vertex of the intersection cone
|
||||
*/
|
||||
for (Point2D.Double pt : points) {
|
||||
// get point as lat, lon on sphere and convert to cartesian
|
||||
Point2D.Double latlon = projection.invert(pt, new Point2D.Double());
|
||||
UnwritableVectorIJK cartesian =
|
||||
CoordConverters.convert(new LatitudinalVector(1, latlon.getY(), latlon.getX()));
|
||||
pts3D.add(cartesian);
|
||||
}
|
||||
|
||||
return pts3D;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a rotated simple cylindrical projection, with the given cone as a reference. That is,
|
||||
* the X axis is the interior point of cone 1 and Y axis is the K vector or J vector. Z completes
|
||||
* the frame.
|
||||
*
|
||||
* @param cone1 the cone used to rotate the projection
|
||||
* @return
|
||||
*/
|
||||
private static Projection createRotatedProjectionFromCone(Cone cone1) {
|
||||
UnwritableVectorIJK x = cone1.getInteriorPoint().createUnitized();
|
||||
UnwritableVectorIJK y = VectorIJK.K;
|
||||
VectorIJK z;
|
||||
try {
|
||||
z = VectorIJK.uCross(x, y);
|
||||
} catch (Exception e) {
|
||||
y = VectorIJK.J;
|
||||
z = VectorIJK.uCross(x, y);
|
||||
}
|
||||
y = VectorIJK.uCross(z, x);
|
||||
RotationMatrixIJK rotMat = new RotationMatrixIJK(x, y, z).transpose();
|
||||
Projection simpleProj = ProjectionBuilders.simpleCylindrical().withLowerBranch(-Math.PI)
|
||||
.withSplit(Math.PI).withOverPoleBorder(Math.toRadians(45)).build();
|
||||
return simpleProj.rotate(rotMat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a projection of the cone on a cylindrical projection. Test if the projection of the
|
||||
* supplied point lies inside the outline of the projected cone.
|
||||
*
|
||||
* @param cone
|
||||
* @param point
|
||||
* @param step step size used to generated the projected outline of the cone
|
||||
* @return true if the point is contained within the cone.
|
||||
*/
|
||||
public static boolean contains(Cone cone, UnwritableVectorIJK point, double step) {
|
||||
Projection projection = createRotatedProjectionFromCone(cone);
|
||||
|
||||
ClosedPathPlotter conePlotter = Cones.plot(cone);
|
||||
Path2D.Double conePath = projection.projectClosedPath(conePlotter, step, new Path2D.Double());
|
||||
|
||||
Point2D projectedPoint =
|
||||
projection.project(PathPlotters.convert(VectorIJK.subtract(point, cone.getVertex())));
|
||||
|
||||
return conePath.contains(projectedPoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an intersection exists between these two cones
|
||||
*
|
||||
* @param cone1 the first cone to be intersected - the reference cone for the
|
||||
* {@link RotatedShapeProjection}
|
||||
* @param cone2 the second cone to be intersected
|
||||
* @return boolean there is an intersection
|
||||
*/
|
||||
public static boolean intersects(Cone cone1, Cone cone2) {
|
||||
double step = .01;
|
||||
|
||||
/*
|
||||
* Make sure cones have vertex in the same location
|
||||
*/
|
||||
Preconditions.checkArgument(cone1.getVertex().equals(cone2.getVertex()),
|
||||
"The cones to be intersected must have the same vertex");
|
||||
|
||||
Projection projection = createRotatedProjectionFromCone(cone1);
|
||||
Path2D.Double cone1path = getConeAsPath(cone1, step, projection);
|
||||
Path2D.Double cone2path = getConeAsPath(cone2, step, projection);
|
||||
|
||||
/*
|
||||
* convert to JTS Shapes and find intersection
|
||||
*/
|
||||
Function<PathIterator, Geometry> pathToGeometryConverter =
|
||||
JtsUtilities.pathIteratorToGeometryConverter();
|
||||
Geometry jtsShape1 = pathToGeometryConverter.apply(cone1path.getPathIterator(null)).buffer(0);
|
||||
Geometry jtsShape2 = pathToGeometryConverter.apply(cone2path.getPathIterator(null)).buffer(0);
|
||||
|
||||
return jtsShape1.intersects(jtsShape2);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param originalCone the original Cone to flip
|
||||
* @return a new Cone with a new vertex, but whose edges end at the same points as the original
|
||||
* cone
|
||||
*/
|
||||
public static Cone flipToNewVertex(Cone originalCone, UnwritableVectorIJK newVertex) {
|
||||
|
||||
|
||||
|
||||
Cone newCone = new Cone() {
|
||||
UnwritableVectorIJK oldVertex = originalCone.getVertex();
|
||||
UnwritableVectorIJK oldInterior = originalCone.getInteriorPoint();
|
||||
|
||||
UnwritableVectorIJK oldVertexToNewVertex = VectorIJK.subtract(newVertex, oldVertex);
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public UnwritableVectorIJK getVertex() {
|
||||
return newVertex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnwritableInterval getParameterDomain() {
|
||||
return originalCone.getParameterDomain();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnwritableVectorIJK getInteriorPoint() {
|
||||
return flip(oldInterior);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnwritableVectorIJK getEdge(double parameter) {
|
||||
return flip(originalCone.getEdge(parameter));
|
||||
}
|
||||
|
||||
private UnwritableVectorIJK flip(UnwritableVectorIJK edgeVector) {
|
||||
return VectorIJK.subtract(edgeVector, oldVertexToNewVertex);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
return newCone;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a cone whos edges intercept a body from a cone in freespace and a body ellipsoid.
|
||||
* Intercect the given cone with the limb cone as seen from the cone vertex. Trace the edges of
|
||||
* the resulting intersected cone down onto the surface of the body. Then create a cone from this
|
||||
* resultant cone
|
||||
*
|
||||
* @param cone the cone to trace down to the body
|
||||
* @param bodyEllipsoid the body
|
||||
* @param coneStep the step used to walk around the given cone
|
||||
* @param limbStep the step used to walk around the limb
|
||||
* @return
|
||||
*/
|
||||
public static Optional<Cone> createConeOnBody(Cone cone, Ellipsoid bodyEllipsoid, double coneStep,
|
||||
double limbStep) {
|
||||
Ellipse limb = bodyEllipsoid.computeLimb(cone.getVertex(), Ellipse.create());
|
||||
// contracting the limb by a small amount to fix round off error in the
|
||||
// intersection computer
|
||||
limb.setToGenerating(limb.getCenter(), limb.getSemiMajorAxis().scale(0.9999999),
|
||||
limb.getSemiMinorAxis().scale(0.9999999));
|
||||
limb.offset(cone.getVertex().createNegated());
|
||||
|
||||
/*
|
||||
* Create a limb cone and intersect with the given cone
|
||||
*/
|
||||
Cone limbCone = Cones.createEllipticalCone(cone.getVertex(), limb);
|
||||
Optional<Cone> intersected = Cones.intersect(cone, coneStep, limbCone, limbStep);
|
||||
|
||||
|
||||
/*
|
||||
* Trace edges of resulting cone down to europa to draw the points on the body
|
||||
*/
|
||||
if (intersected.isPresent()) { // empty cone = no intersection - nothing to draw
|
||||
try {
|
||||
PolygonalCone intersectedCone = (PolygonalCone) intersected.get();
|
||||
List<UnwritableVectorIJK> cartesianSCpts = intersectedCone.getCorners();
|
||||
List<UnwritableVectorIJK> ptsOnEuropa = Lists.newArrayList();
|
||||
for (UnwritableVectorIJK cartesian : Iterables.limit(cartesianSCpts,
|
||||
cartesianSCpts.size())) {
|
||||
VectorIJK thisPt =
|
||||
bodyEllipsoid.compute(intersectedCone.getVertex(), cartesian, new VectorIJK());
|
||||
ptsOnEuropa.add(thisPt);
|
||||
}
|
||||
// get interior point on Europa
|
||||
VectorIJK interior = bodyEllipsoid.compute(intersectedCone.getVertex(),
|
||||
intersectedCone.getInteriorPoint(), new VectorIJK());
|
||||
|
||||
/*
|
||||
* Create a cone on the body
|
||||
*/
|
||||
PolygonalCone polyCone = Cones.createPolygonalCone(VectorIJK.ZERO, interior, ptsOnEuropa);
|
||||
return Optional.of(polyCone);
|
||||
} catch (NoIntersectionException e) {
|
||||
// TODO this should never happen - throw an error?
|
||||
System.out.println("no intersection on body");
|
||||
return Optional.empty();
|
||||
}
|
||||
} else {
|
||||
// the cone doesn't intersect the body
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a path plotter from a cone in freespace and a body ellipsoid. Subtract the given cone
|
||||
* from the limb cone as seen from the cone vertex. Trace the edges of the resulting intersected
|
||||
* cone down onto the surface of the body. Then create a Cone from the resulting list of points.
|
||||
*
|
||||
* @param cone the cone to trace down to the body
|
||||
* @param bodyEllipsoid the body
|
||||
* @param coneStep the step used to walk around the given cone
|
||||
* @param limbStep the step used to walk around the limb
|
||||
* @return
|
||||
*/
|
||||
public static Optional<Cone> subtractConeFromBody(Cone cone, Ellipsoid bodyEllipsoid,
|
||||
double coneStep, double limbStep) {
|
||||
UnwritableVectorIJK source = cone.getVertex();
|
||||
Ellipse limb = bodyEllipsoid.computeLimb(source, Ellipse.create());
|
||||
// contracting the limb by a small amount to fix round off error in the
|
||||
// intersection computer
|
||||
limb.setToGenerating(limb.getCenter(), limb.getSemiMajorAxis().scale(0.9999999),
|
||||
limb.getSemiMinorAxis().scale(0.9999999));
|
||||
limb.offset(source.createNegated());
|
||||
|
||||
/*
|
||||
* Create a limb cone and subtract the given cone from it
|
||||
*/
|
||||
Cone limbCone = Cones.createEllipticalCone(source, limb);
|
||||
List<Cone> subtractedList = Cones.subtract(limbCone, limbStep, cone, coneStep);
|
||||
if (subtractedList.size() == 0) {
|
||||
return Optional.empty();
|
||||
}
|
||||
if (subtractedList.size() != 1) {
|
||||
throw new RuntimeException("I WASNT EXPECTING THIS"); // TODO
|
||||
} else {
|
||||
|
||||
Cone subtracted = subtractedList.get(0);
|
||||
|
||||
|
||||
try {
|
||||
PolygonalCone intersectedCone = (PolygonalCone) subtracted;
|
||||
List<UnwritableVectorIJK> cartesianSCpts = intersectedCone.getCorners();
|
||||
List<UnwritableVectorIJK> ptsOnEuropa = Lists.newArrayList();
|
||||
for (UnwritableVectorIJK cartesian : cartesianSCpts) {
|
||||
ptsOnEuropa
|
||||
.add(bodyEllipsoid.compute(intersectedCone.getVertex(), cartesian, new VectorIJK()));
|
||||
}
|
||||
|
||||
// get interior point on Europa
|
||||
VectorIJK interior = bodyEllipsoid.compute(intersectedCone.getVertex(),
|
||||
intersectedCone.getInteriorPoint(), new VectorIJK());
|
||||
|
||||
|
||||
PolygonalCone polyCone = Cones.createPolygonalCone(VectorIJK.ZERO, interior, ptsOnEuropa);
|
||||
return Optional.of(polyCone);
|
||||
} catch (NoIntersectionException e) {
|
||||
// TODO this should never happen - throw an error?
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Optional<Cone> union(List<Cone> toUnion, double stepSize) {
|
||||
Optional<Cone> union = Optional.of(toUnion.get(0));
|
||||
|
||||
if (toUnion.size() > 1) {
|
||||
|
||||
/*
|
||||
* get the middle cone to use its vertex and use as the projection cone
|
||||
*/
|
||||
Cone midCone = toUnion.get(toUnion.size() / 2);
|
||||
|
||||
Function<PathIterator, Geometry> pathToGeometryConverter =
|
||||
JtsUtilities.pathIteratorToGeometryConverter();
|
||||
|
||||
Projection projection = createRotatedProjectionFromCone(midCone);
|
||||
|
||||
List<Geometry> jtsCones = Lists.newArrayList();
|
||||
for (int iCone = 0; iCone < toUnion.size(); iCone++) {
|
||||
Path2D.Double conePath = getConeAsPath(toUnion.get(iCone), stepSize, projection);
|
||||
jtsCones.add(pathToGeometryConverter.apply(conePath.getPathIterator(null)).buffer(0));
|
||||
}
|
||||
|
||||
Geometry unionGeom = CascadedPolygonUnion.union(jtsCones);
|
||||
union = Cones.createFromGeometry(unionGeom, projection, midCone.getVertex());
|
||||
}
|
||||
return union;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
26
src/main/java/picante/math/cones/DerivativeProjection.java
Normal file
26
src/main/java/picante/math/cones/DerivativeProjection.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package picante.math.cones;
|
||||
|
||||
abstract class DerivativeProjection extends AbstractProjection {
|
||||
|
||||
private final Projection origin;
|
||||
|
||||
DerivativeProjection(Projection originalProjection) {
|
||||
super();
|
||||
this.origin = originalProjection;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Recursively unwrap the underlying projection wrappers. This is safe, because there is no way
|
||||
* outside of the {@link Projection#flip()},
|
||||
* {@link Projection#clip(picante.math.intervals.UnwritableInterval, picante.math.intervals.UnwritableInterval)},
|
||||
* or {@link Projection#rotate(picante.math.vectorspace.UnwritableRotationMatrixIJK) methods
|
||||
* that allow these to be created.
|
||||
*/
|
||||
@Override
|
||||
public Class<? extends Projection> getOriginalClass() {
|
||||
return origin.getOriginalClass();
|
||||
}
|
||||
|
||||
}
|
||||
68
src/main/java/picante/math/cones/EllipticalCone.java
Normal file
68
src/main/java/picante/math/cones/EllipticalCone.java
Normal file
@@ -0,0 +1,68 @@
|
||||
package picante.math.cones;
|
||||
|
||||
import picante.math.intervals.UnwritableInterval;
|
||||
import picante.math.vectorspace.UnwritableVectorIJK;
|
||||
import picante.math.vectorspace.VectorIJK;
|
||||
|
||||
/**
|
||||
* An implementation of a {@link Cone} that has an elliptical shape.
|
||||
*
|
||||
* The Cone has a vertex, an interior point, and two "generating vectors". These vectors are
|
||||
* orthogonal to each other and to the interior point, and will be used in the calculation of edge
|
||||
* vectors. For any parameter theta in [0, 2*PI], the edge vector is:
|
||||
*
|
||||
* boresight + cos(theta) * axis1 + sin(theta) * axis2
|
||||
*
|
||||
* @author osheacm1
|
||||
*
|
||||
*/
|
||||
class EllipticalCone implements Cone {
|
||||
|
||||
private final UnwritableVectorIJK vertex;
|
||||
private final UnwritableVectorIJK boresight;
|
||||
private UnwritableVectorIJK axis1;
|
||||
private UnwritableVectorIJK axis2;
|
||||
|
||||
/**
|
||||
* Construct an EllipticalCone from the vertex, boresight, and two orthogonal axes
|
||||
*
|
||||
* @param vertex the vector to the vertex of the cone
|
||||
* @param boresight the boresight of the instrument, or the axis of the cone - pointed down the
|
||||
* middle of the cone
|
||||
* @param axis1 one "generating vector" - scaled by the tangent of a cone half angle
|
||||
* @param axis2 the second "generating vector" - scaled by the tangent of the cone's other half
|
||||
* angle
|
||||
*/
|
||||
EllipticalCone(UnwritableVectorIJK vertex, UnwritableVectorIJK boresight,
|
||||
UnwritableVectorIJK axis1, UnwritableVectorIJK axis2) {
|
||||
super();
|
||||
this.vertex = vertex;
|
||||
this.boresight = boresight;
|
||||
this.axis1 = axis1;
|
||||
this.axis2 = axis2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnwritableVectorIJK getVertex() {
|
||||
return vertex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnwritableVectorIJK getEdge(double theta) {
|
||||
VectorIJK edge =
|
||||
VectorIJK.combine(1.0, boresight, Math.cos(theta), axis1, Math.sin(theta), axis2);
|
||||
|
||||
return edge.createUnitized();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnwritableInterval getParameterDomain() {
|
||||
return new UnwritableInterval(0, 2 * Math.PI);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnwritableVectorIJK getInteriorPoint() {
|
||||
return boresight;
|
||||
}
|
||||
|
||||
}
|
||||
243
src/main/java/picante/math/cones/GeometrySanitizer.java
Normal file
243
src/main/java/picante/math/cones/GeometrySanitizer.java
Normal file
@@ -0,0 +1,243 @@
|
||||
package picante.math.cones;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.locationtech.jts.geom.Geometry;
|
||||
import org.locationtech.jts.geom.GeometryCollection;
|
||||
import org.locationtech.jts.geom.GeometryFactory;
|
||||
import org.locationtech.jts.geom.LineString;
|
||||
import org.locationtech.jts.geom.LinearRing;
|
||||
import org.locationtech.jts.geom.MultiLineString;
|
||||
import org.locationtech.jts.geom.MultiPoint;
|
||||
import org.locationtech.jts.geom.MultiPolygon;
|
||||
import org.locationtech.jts.geom.Point;
|
||||
import org.locationtech.jts.geom.Polygon;
|
||||
|
||||
import com.google.common.collect.HashMultiset;
|
||||
import com.google.common.collect.ImmutableListMultimap;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Multiset;
|
||||
|
||||
/**
|
||||
* Cleans up a list of {@link Geometry} instances into a single one based of an analysis of the
|
||||
* individual elements of the input list.
|
||||
* <p>
|
||||
* <ul>
|
||||
* <li>A list of {@link Point}s are turned into a single {@link MultiPoint}, unless the list has a
|
||||
* single element.</li>
|
||||
* <li>A list of {@link LineString}s are turned into a single {@link MultiLineString}, unless the
|
||||
* list has a single element.</li>
|
||||
* <li>A list of {@link Polygons}s are turned into a single {@link MultiPolygon}, unless the list
|
||||
* has a single element, or if the {@link LinearRing}s overlap in some unusual way. The
|
||||
* {@link MultiPolygon} will be built up by examining which {@link Polygon}s cover one another as
|
||||
* holes.</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
* <p>
|
||||
* If the above criteria aren't met by the input, then this function just returns
|
||||
* {@link GeometryCollection} with the input list of {@link Geometry} instances.
|
||||
* </p>
|
||||
*/
|
||||
class GeometrySanitizer implements Function<List<? extends Geometry>, Geometry> {
|
||||
|
||||
private final GeometryFactory geometryFactory;
|
||||
|
||||
GeometrySanitizer(GeometryFactory geometryFactory) {
|
||||
super();
|
||||
this.geometryFactory = geometryFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Geometry apply(List<? extends Geometry> geometries) {
|
||||
|
||||
/*
|
||||
* If the list is empty, return an empty geometry.
|
||||
*/
|
||||
if (geometries.size() == 0) {
|
||||
return geometryFactory.createPoint();
|
||||
}
|
||||
|
||||
Class<? extends Geometry> type = isHomogeneous(geometries);
|
||||
|
||||
if (type == null) {
|
||||
return geometryFactory
|
||||
.createGeometryCollection(geometries.toArray(new Geometry[geometries.size()]));
|
||||
}
|
||||
|
||||
/*
|
||||
* If all the entries are points, then return a MultiPoint.
|
||||
*/
|
||||
if (Point.class.equals(type)) {
|
||||
if (geometries.size() == 1) {
|
||||
return geometries.get(0);
|
||||
}
|
||||
return geometryFactory.createMultiPoint(geometries.toArray(new Point[geometries.size()]));
|
||||
}
|
||||
|
||||
/*
|
||||
* If all the entries are strictly linestrings, then return a MultiLineString.
|
||||
*/
|
||||
if (LineString.class.equals(type)) {
|
||||
if (geometries.size() == 1) {
|
||||
return geometries.get(0);
|
||||
}
|
||||
return geometryFactory
|
||||
.createMultiLineString(geometries.toArray(new LineString[geometries.size()]));
|
||||
}
|
||||
|
||||
/*
|
||||
* If all the entries are strictly linear rings, then either return a MultiLineString, Polygon,
|
||||
* or MultiPolygon depending on the relationships between the linear rings.
|
||||
*/
|
||||
if (LinearRing.class.equals(type)) {
|
||||
return processCoverings(geometries);
|
||||
}
|
||||
|
||||
/*
|
||||
* If we reach here just throw everything into a GeometryCollection, as it's beyond what this
|
||||
* class attempts to support.
|
||||
*/
|
||||
return geometryFactory
|
||||
.createGeometryCollection(geometries.toArray(new Geometry[geometries.size()]));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if all the entries are the same sub-class of geometry.
|
||||
* <p>
|
||||
* This method uses {@link Class#equals(Object)}, because it treats sub-classes differently. That
|
||||
* is to say, a {@link LinearRing} is not the same as a {@link LineString}.
|
||||
* </p>
|
||||
*
|
||||
* @param geometries
|
||||
*
|
||||
* @return true of all entries in geometries are the exactly same {@link Class}, false otherwise.
|
||||
*/
|
||||
Class<? extends Geometry> isHomogeneous(List<? extends Geometry> geometries) {
|
||||
|
||||
Class<? extends Geometry> result = geometries.get(0).getClass();
|
||||
|
||||
for (Geometry geometry : geometries) {
|
||||
if (!result.equals(geometry.getClass())) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
Geometry processCoverings(List<? extends Geometry> rings) {
|
||||
|
||||
/*
|
||||
* This is a bit of a mess. Determine if all of the rings are independent or holes in other
|
||||
* rings. If that's the case then return a MultiPolygon, otherwise return a MultiLineString.
|
||||
*/
|
||||
ImmutableListMultimap<Integer, Integer> coverings = createCoverings(rings);
|
||||
|
||||
/*
|
||||
* Collect all the indices of rings that are classified as holes of other rings.
|
||||
*/
|
||||
Multiset<Integer> holes = HashMultiset.create(rings.size());
|
||||
holes.addAll(coverings.values());
|
||||
|
||||
List<Polygon> polygons = new ArrayList<>(rings.size());
|
||||
|
||||
final int ringsLength = rings.size();
|
||||
|
||||
/*
|
||||
* Iterate over the entire list of rings, and only add polygons for those that do not appear as
|
||||
* holes of another ring.
|
||||
*/
|
||||
for (int i = 0; i < ringsLength; i++) {
|
||||
|
||||
int count = holes.count(i);
|
||||
|
||||
/*
|
||||
* If there is a single hole that appears more than once as a hole of two distinct linear
|
||||
* rings, then something weird is going on. Punt and return a MultiLineString.
|
||||
*/
|
||||
if (count > 1) {
|
||||
return geometryFactory.createMultiLineString(rings.toArray(new LineString[rings.size()]));
|
||||
}
|
||||
|
||||
/*
|
||||
* If this index does not appear at all in the holes multiset, then it should be converted
|
||||
* from a linear ring to a proper polygon.
|
||||
*/
|
||||
if (count == 0) {
|
||||
|
||||
/*
|
||||
* If it has holes, then include them.
|
||||
*/
|
||||
if (coverings.containsKey(i)) {
|
||||
|
||||
List<Integer> holeIndices = coverings.get(i);
|
||||
LinearRing[] holeArray = new LinearRing[holeIndices.size()];
|
||||
for (int j = 0; j < holeIndices.size(); j++) {
|
||||
/*
|
||||
* Check to see if this "hole" is covered by multiple other rings in the set. If it is,
|
||||
* then return null, indicating that a basic GeometryCollection is to be returned.
|
||||
*/
|
||||
holeArray[j] = (LinearRing) rings.get(holeIndices.get(j));
|
||||
}
|
||||
polygons.add(geometryFactory.createPolygon((LinearRing) rings.get(i), holeArray));
|
||||
|
||||
} else {
|
||||
polygons.add(geometryFactory.createPolygon((LinearRing) rings.get(i)));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (polygons.size() == 1) {
|
||||
return polygons.get(0);
|
||||
}
|
||||
|
||||
return geometryFactory.createMultiPolygon(polygons.toArray(new Polygon[polygons.size()]));
|
||||
|
||||
|
||||
}
|
||||
|
||||
ImmutableListMultimap<Integer, Integer> createCoverings(List<? extends Geometry> rings) {
|
||||
|
||||
ImmutableListMultimap.Builder<Integer, Integer> resultBuilder = ImmutableListMultimap.builder();
|
||||
|
||||
/*
|
||||
* Convert the input rings to Polygons. This is necessary as LinearRings have no interior.
|
||||
*/
|
||||
List<Polygon> polygons = Lists.newArrayList(
|
||||
Iterables.transform(rings, (r) -> geometryFactory.createPolygon((LinearRing) r)));
|
||||
|
||||
/*
|
||||
* This a bit messy, determine which rings contain each other.
|
||||
*/
|
||||
final int polysLength = polygons.size();
|
||||
for (int i = 0; i < polysLength; i++) {
|
||||
|
||||
Geometry polygon = polygons.get(i);
|
||||
|
||||
for (int j = 0; j < polysLength; j++) {
|
||||
|
||||
Geometry candidate = polygons.get(j);
|
||||
|
||||
/*
|
||||
* Only operate if the two are not the exact same instance.
|
||||
*/
|
||||
if (i != j) {
|
||||
if (polygon.contains(candidate)) {
|
||||
resultBuilder.put(i, j);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
return resultBuilder.build();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
139
src/main/java/picante/math/cones/JtsUtilities.java
Normal file
139
src/main/java/picante/math/cones/JtsUtilities.java
Normal file
@@ -0,0 +1,139 @@
|
||||
package picante.math.cones;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import java.awt.Shape;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.PathIterator;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import org.locationtech.jts.geom.Coordinate;
|
||||
import org.locationtech.jts.geom.CoordinateSequence;
|
||||
import org.locationtech.jts.geom.Geometry;
|
||||
import org.locationtech.jts.geom.GeometryCollection;
|
||||
import org.locationtech.jts.geom.GeometryFactory;
|
||||
import org.locationtech.jts.geom.LineString;
|
||||
import org.locationtech.jts.geom.MultiLineString;
|
||||
import org.locationtech.jts.geom.MultiPolygon;
|
||||
import org.locationtech.jts.geom.Polygon;
|
||||
import org.locationtech.jts.geom.PrecisionModel;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
/**
|
||||
* Class capturing utility methods for manipulating, creating, and converting to and from JTS
|
||||
* {@link Geometry} types.
|
||||
*/
|
||||
class JtsUtilities {
|
||||
|
||||
/**
|
||||
* Simple {@link Geometry} filter that generates exceptions when presented with an empty geometry.
|
||||
*/
|
||||
static final Function<Geometry, Geometry> EXCEPTION_ON_EMPTY_GEOMETRY = (t) -> {
|
||||
if (t.isEmpty()) {
|
||||
throw new IllegalArgumentException("Unable to process empty geometry");
|
||||
}
|
||||
return t;
|
||||
};
|
||||
|
||||
/**
|
||||
* Convenience method to create coordinate arrays from pairs of xy values.
|
||||
*
|
||||
* @param xys double array of { x0, y0, x1, y1, ..., xN, yN }
|
||||
*
|
||||
* @return coordinate array containing (x0,y0), (x1,y1), ... (xN,yN)
|
||||
*/
|
||||
static Coordinate[] createCoordinateArray(double... xys) {
|
||||
checkArgument(xys.length % 2 == 0, "Input array entries must be paired.");
|
||||
Coordinate[] result = new Coordinate[xys.length / 2];
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
result[i] = new Coordinate(xys[2 * i], xys[2 * i + 1]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to create {@link PackedCoordinateSequence}s from pairs of xy values.
|
||||
*
|
||||
* @param xys double array of { x0, y0, x1, y1, ..., xN, yN }
|
||||
*
|
||||
* @return a {@link PackedCoordinateSequence} containing the pairs of coordinates: (x0, y0), (x1,
|
||||
* y1), ... (xN,yN)
|
||||
*/
|
||||
static CoordinateSequence createPackedCoordinateSequence(double... xys) {
|
||||
checkArgument(xys.length % 2 == 0, "Input array entries must be paired.");
|
||||
return new ModifiedPackedCoordinateSequence.Double(xys, 2, 0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Geometry factory is thread-safe. This one is configured to use double precision.
|
||||
* <p>
|
||||
* This implementation should be thread-safe, from Martin Davis: "GeometryFactory is
|
||||
* immutable so is thread safe. CoordinateSequenceFactory is an interface, so it's up to the
|
||||
* implementation. But normally they should be thread-safe."
|
||||
* (<a href= "https://gitter.im/locationtech/jts?at=5d9c3f783220922ffb4b88b8">link</a>)
|
||||
* </p>
|
||||
*/
|
||||
static GeometryFactory PACKED_DOUBLE_GEOMETRY_FACTORY =
|
||||
new GeometryFactory(new PrecisionModel(PrecisionModel.FLOATING), 0,
|
||||
ModifiedPackedCoordinateSequenceFactory.DOUBLE_FACTORY);
|
||||
|
||||
/**
|
||||
* Simple {@link PathIterator} filter that throws an exception when the path is done, i.e. empty.
|
||||
*/
|
||||
static Function<PathIterator, PathIterator> EXCEPTION_ON_EMPTY_PATHITERTOR = (t) -> {
|
||||
if (t.isDone()) {
|
||||
throw new IllegalArgumentException("Unable to process empty path iterator");
|
||||
}
|
||||
return t;
|
||||
};
|
||||
|
||||
/**
|
||||
* Simple function to convert {@link Shape} to {@link PathIterator}. This simply invokes:
|
||||
* {@link Shape#getPathIterator(AffineTransform)} with the supplied argument as null;
|
||||
*/
|
||||
static Function<Shape, PathIterator> SHAPE_ITERATOR = (s) -> s.getPathIterator(null);
|
||||
|
||||
/**
|
||||
* Function that filters out isolated points from a list of {@link CoordinateSequence}s.
|
||||
* <p>
|
||||
* Individual points really have no place being in shapes that are to be rendered in Java2D,
|
||||
* though the API allows for it.
|
||||
* </p>
|
||||
*/
|
||||
static Function<List<CoordinateSequence>, List<CoordinateSequence>> POINT_REMOVER =
|
||||
(l) -> ImmutableList.copyOf(Iterables.filter(l, (cs) -> cs.size() > 1));
|
||||
|
||||
|
||||
/**
|
||||
* Supplies a function that converts a path iterator to a geometry.
|
||||
*/
|
||||
static Function<PathIterator, Geometry> GEOMETRY_CONVERTER = EXCEPTION_ON_EMPTY_PATHITERTOR
|
||||
.andThen(new PathSplitter(ModifiedPackedCoordinateSequenceFactory.DOUBLE_FACTORY))
|
||||
.andThen(POINT_REMOVER).andThen(new SequencesConverter(PACKED_DOUBLE_GEOMETRY_FACTORY)
|
||||
.andThen(new GeometrySanitizer(PACKED_DOUBLE_GEOMETRY_FACTORY)));
|
||||
|
||||
// static Function<PathIterator, Geometry> GEOMETRY_CONVERTER = EXCEPTION_ON_EMPTY_PATHITERTOR
|
||||
// .andThen(new PathSplitter(PackedCoordinateSequenceFactory.DOUBLE_FACTORY))
|
||||
// .andThen(POINT_REMOVER).andThen(new SequencesConverter(PACKED_DOUBLE_GEOMETRY_FACTORY)
|
||||
// .andThen(new GeometrySanitizer(PACKED_DOUBLE_GEOMETRY_FACTORY)));
|
||||
|
||||
|
||||
/**
|
||||
* Converts a {@link PathIterator} to a {@link Geometry}.
|
||||
* <p>
|
||||
* Isolated points are removed prior to conversion. Open paths are converted to
|
||||
* {@link LineString}. Closed paths are converted to {@link Polygon}s. If multiple of either are
|
||||
* required, then the corresponding {@link MultiLineString} or {@link MultiPolygon} are used
|
||||
* instead. If anything goes unexpectedly, then an {@link UnsupportedOperationException} will be
|
||||
* thrown or a {@link GeometryCollection} with a variety of different types will be returned.
|
||||
* </p>
|
||||
*
|
||||
* @return a new function to convert path iterators.
|
||||
*/
|
||||
public static Function<PathIterator, Geometry> pathIteratorToGeometryConverter() {
|
||||
return GEOMETRY_CONVERTER;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
package picante.math.cones;
|
||||
|
||||
import org.locationtech.jts.geom.Coordinate;
|
||||
import org.locationtech.jts.geom.CoordinateXY;
|
||||
import org.locationtech.jts.geom.CoordinateXYZM;
|
||||
import org.locationtech.jts.geom.impl.PackedCoordinateSequence;
|
||||
|
||||
/**
|
||||
* Class enclosing two simple extensions of {@link PackedCoordinateSequence.Double} and
|
||||
* {@link PackedCoordinateSequence.Float}.
|
||||
* <p>
|
||||
* The purpose of this class is to work around the assignment problem, when a packed coordinate
|
||||
* sequence of higher dimension has been requested to set one of lower dimension. The current JTS
|
||||
* code fails, but this method will allow it--setting the higher dimensional component to
|
||||
* {@link java.lang.Double#NaN}.
|
||||
* </p>
|
||||
*/
|
||||
class ModifiedPackedCoordinateSequence {
|
||||
|
||||
public static class Double extends PackedCoordinateSequence.Double {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Safely converts an array of {@link Coordinate} to a double array, if the dimension of all
|
||||
* input coordinates is less than that of the data store.
|
||||
*
|
||||
* @param coordinates
|
||||
* @param dimension
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
static double[] convert(Coordinate[] coordinates, int dimension) {
|
||||
if (coordinates == null) {
|
||||
coordinates = new Coordinate[0];
|
||||
}
|
||||
|
||||
double[] coords = new double[coordinates.length * dimension];
|
||||
for (int i = 0; i < coordinates.length; i++) {
|
||||
coords[i * dimension] = coordinates[i].x;
|
||||
if (dimension >= 2) {
|
||||
coords[i * dimension + 1] = coordinates[i].y;
|
||||
}
|
||||
if (dimension >= 3) {
|
||||
coords[i * dimension + 2] = getOrdinate(coordinates[i], 2);
|
||||
}
|
||||
if (dimension >= 4) {
|
||||
coords[i * dimension + 3] = getOrdinate(coordinates[i], 3);
|
||||
}
|
||||
}
|
||||
return coords;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the ordinate of the supplied coordinate, or NaN if it can not be provided.
|
||||
*
|
||||
* @param coord
|
||||
* @param ordinate
|
||||
*
|
||||
* @return the value at ordinate in coord, or {@link java.lang.Double#NaN} if not available
|
||||
*/
|
||||
static double getOrdinate(Coordinate coord, int ordinate) {
|
||||
|
||||
if (coord instanceof CoordinateXY) {
|
||||
return java.lang.Double.NaN;
|
||||
}
|
||||
|
||||
if (ordinate == 2) {
|
||||
return coord.getOrdinate(2);
|
||||
}
|
||||
|
||||
if (ordinate == 3) {
|
||||
if (coord instanceof CoordinateXYZM) {
|
||||
return coord.getOrdinate(3);
|
||||
}
|
||||
}
|
||||
|
||||
return java.lang.Double.NaN;
|
||||
|
||||
}
|
||||
|
||||
Double(Coordinate[] coordinates, int dimension, int measures) {
|
||||
this(convert(coordinates, dimension), dimension, measures);
|
||||
}
|
||||
|
||||
Double(Coordinate[] coordinates, int dimension) {
|
||||
this(coordinates, dimension, 0);
|
||||
}
|
||||
|
||||
Double(Coordinate[] coordinates) {
|
||||
this(coordinates, 3);
|
||||
}
|
||||
|
||||
Double(double[] coords, int dimension, int measures) {
|
||||
super(coords, dimension, measures);
|
||||
}
|
||||
|
||||
Double(float[] coordinates, int dimension, int measures) {
|
||||
super(coordinates, dimension, measures);
|
||||
}
|
||||
|
||||
Double(int size, int dimension, int measures) {
|
||||
super(size, dimension, measures);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static class Float extends PackedCoordinateSequence.Float {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Safely converts an array of {@link Coordinate} to a float array, if the dimension of all
|
||||
* input coordinates is less than that of the data store.
|
||||
*
|
||||
* @param coordinates
|
||||
* @param dimension
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
static float[] convert(Coordinate[] coordinates, int dimension) {
|
||||
if (coordinates == null) {
|
||||
coordinates = new Coordinate[0];
|
||||
}
|
||||
|
||||
float[] coords = new float[coordinates.length * dimension];
|
||||
for (int i = 0; i < coordinates.length; i++) {
|
||||
coords[i * dimension] = (float) coordinates[i].x;
|
||||
if (dimension >= 2) {
|
||||
coords[i * dimension + 1] = (float) coordinates[i].y;
|
||||
}
|
||||
if (dimension >= 3) {
|
||||
coords[i * dimension + 2] = getOrdinate(coordinates[i], 2);
|
||||
}
|
||||
if (dimension >= 4) {
|
||||
coords[i * dimension + 3] = getOrdinate(coordinates[i], 3);
|
||||
}
|
||||
}
|
||||
return coords;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the ordinate of the supplied coordinate, or NaN if it can not be provided.
|
||||
*
|
||||
* @param coord
|
||||
* @param ordinate
|
||||
*
|
||||
* @return the value at ordinate in coord cast to a float, or {@link java.lang.Float#NaN} if not
|
||||
* available
|
||||
*/
|
||||
static float getOrdinate(Coordinate coord, int ordinate) {
|
||||
|
||||
if (coord instanceof CoordinateXY) {
|
||||
return java.lang.Float.NaN;
|
||||
}
|
||||
|
||||
if (ordinate == 2) {
|
||||
return (float) coord.getOrdinate(2);
|
||||
}
|
||||
|
||||
if (ordinate == 3) {
|
||||
if (coord instanceof CoordinateXYZM) {
|
||||
return (float) coord.getOrdinate(3);
|
||||
}
|
||||
}
|
||||
|
||||
return java.lang.Float.NaN;
|
||||
|
||||
}
|
||||
|
||||
|
||||
Float(Coordinate[] coordinates, int dimension, int measures) {
|
||||
this(convert(coordinates, dimension), dimension, measures);
|
||||
}
|
||||
|
||||
Float(Coordinate[] coordinates, int dimension) {
|
||||
this(coordinates, dimension, 0);
|
||||
}
|
||||
|
||||
Float(Coordinate[] coordinates) {
|
||||
this(coordinates, 3);
|
||||
}
|
||||
|
||||
Float(double[] coords, int dimension, int measures) {
|
||||
super(coords, dimension, measures);
|
||||
}
|
||||
|
||||
Float(float[] coordinates, int dimension, int measures) {
|
||||
super(coordinates, dimension, measures);
|
||||
}
|
||||
|
||||
Float(int size, int dimension, int measures) {
|
||||
super(size, dimension, measures);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package picante.math.cones;
|
||||
|
||||
import org.locationtech.jts.geom.Coordinate;
|
||||
import org.locationtech.jts.geom.CoordinateSequence;
|
||||
import org.locationtech.jts.geom.Coordinates;
|
||||
import org.locationtech.jts.geom.impl.PackedCoordinateSequenceFactory;
|
||||
|
||||
/**
|
||||
* Implementation of {@link PackedCoordinateSequenceFactory} that provides
|
||||
* {@link ModifiedPackedCoordinateSequence}s
|
||||
*/
|
||||
class ModifiedPackedCoordinateSequenceFactory extends PackedCoordinateSequenceFactory {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public static final int DOUBLE = 0;
|
||||
public static final int FLOAT = 1;
|
||||
|
||||
public static final ModifiedPackedCoordinateSequenceFactory DOUBLE_FACTORY =
|
||||
new ModifiedPackedCoordinateSequenceFactory(DOUBLE);
|
||||
|
||||
public static final ModifiedPackedCoordinateSequenceFactory FLOAT_FACTORY =
|
||||
new ModifiedPackedCoordinateSequenceFactory(FLOAT);
|
||||
|
||||
private int type = DOUBLE;
|
||||
|
||||
/**
|
||||
* Creates a new PackedCoordinateSequenceFactory of type DOUBLE.
|
||||
*/
|
||||
public ModifiedPackedCoordinateSequenceFactory() {
|
||||
this(DOUBLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new PackedCoordinateSequenceFactory of the given type. Acceptable type values are
|
||||
* {@linkplain PackedCoordinateSequenceFactory#Float}or
|
||||
* {@linkplain PackedCoordinateSequenceFactory#Double}
|
||||
*/
|
||||
public ModifiedPackedCoordinateSequenceFactory(int type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getType() {
|
||||
return super.getType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CoordinateSequence create(Coordinate[] coordinates) {
|
||||
int dimension = 3;
|
||||
int measures = 0;
|
||||
if (coordinates != null && coordinates.length > 1 && coordinates[0] != null) {
|
||||
Coordinate first = coordinates[0];
|
||||
dimension = Coordinates.dimension(first);
|
||||
measures = Coordinates.measures(first);
|
||||
}
|
||||
if (type == DOUBLE) {
|
||||
return new ModifiedPackedCoordinateSequence.Double(coordinates, dimension, measures);
|
||||
} else {
|
||||
return new ModifiedPackedCoordinateSequence.Float(coordinates, dimension, measures);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CoordinateSequence create(CoordinateSequence coordSeq) {
|
||||
int dimension = coordSeq.getDimension();
|
||||
int measures = coordSeq.getMeasures();
|
||||
if (type == DOUBLE) {
|
||||
return new ModifiedPackedCoordinateSequence.Double(coordSeq.toCoordinateArray(), dimension,
|
||||
measures);
|
||||
} else {
|
||||
return new ModifiedPackedCoordinateSequence.Float(coordSeq.toCoordinateArray(), dimension,
|
||||
measures);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CoordinateSequence create(double[] packedCoordinates, int dimension) {
|
||||
return create(packedCoordinates, dimension, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CoordinateSequence create(double[] packedCoordinates, int dimension, int measures) {
|
||||
if (type == DOUBLE) {
|
||||
return new ModifiedPackedCoordinateSequence.Double(packedCoordinates, dimension, measures);
|
||||
} else {
|
||||
return new ModifiedPackedCoordinateSequence.Float(packedCoordinates, dimension, measures);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CoordinateSequence create(float[] packedCoordinates, int dimension) {
|
||||
return create(packedCoordinates, dimension, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CoordinateSequence create(float[] packedCoordinates, int dimension, int measures) {
|
||||
if (type == DOUBLE) {
|
||||
return new ModifiedPackedCoordinateSequence.Double(packedCoordinates, dimension, measures);
|
||||
} else {
|
||||
return new ModifiedPackedCoordinateSequence.Float(packedCoordinates, dimension, measures);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CoordinateSequence create(int size, int dimension) {
|
||||
if (type == DOUBLE) {
|
||||
return new ModifiedPackedCoordinateSequence.Double(size, dimension, 0);
|
||||
} else {
|
||||
return new ModifiedPackedCoordinateSequence.Float(size, dimension, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CoordinateSequence create(int size, int dimension, int measures) {
|
||||
if (type == DOUBLE) {
|
||||
return new ModifiedPackedCoordinateSequence.Double(size, dimension, measures);
|
||||
} else {
|
||||
return new ModifiedPackedCoordinateSequence.Float(size, dimension, measures);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
35
src/main/java/picante/math/cones/PathPlotter.java
Normal file
35
src/main/java/picante/math/cones/PathPlotter.java
Normal file
@@ -0,0 +1,35 @@
|
||||
package picante.math.cones;
|
||||
|
||||
import java.awt.geom.Point2D;
|
||||
import picante.math.intervals.UnwritableInterval;
|
||||
|
||||
/**
|
||||
* Interface describing a parameterized 2D path.
|
||||
*/
|
||||
interface PathPlotter {
|
||||
|
||||
/**
|
||||
* Computes a point along a parameterized path.
|
||||
*
|
||||
* @param p the value of the parameter at which to compute the point
|
||||
* @param buffer a buffer to receive the results
|
||||
*
|
||||
* @return a reference to the supplied buffer for convenience of method chaining
|
||||
*
|
||||
* @throws IllegalArgumentException if p is not in the domain supported by this function.
|
||||
*/
|
||||
Point2D plot(double p, Point2D buffer);
|
||||
|
||||
default Point2D plot(double p) {
|
||||
return plot(p, new Point2D.Double());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the domain over which the path's {@link PathPlotter#plot(double, Point2D)} function
|
||||
* is to be invoked.
|
||||
*
|
||||
* @return the domain over which the plotter is to be plotted
|
||||
*/
|
||||
UnwritableInterval getDomain();
|
||||
|
||||
}
|
||||
129
src/main/java/picante/math/cones/PathPlotters.java
Normal file
129
src/main/java/picante/math/cones/PathPlotters.java
Normal file
@@ -0,0 +1,129 @@
|
||||
package picante.math.cones;
|
||||
|
||||
import java.awt.geom.Point2D;
|
||||
import picante.math.coords.CoordConverters;
|
||||
import picante.math.coords.LatitudinalVector;
|
||||
import picante.math.intervals.UnwritableInterval;
|
||||
import picante.math.vectorspace.UnwritableRotationMatrixIJK;
|
||||
import picante.math.vectorspace.UnwritableVectorIJK;
|
||||
import picante.math.vectorspace.VectorIJK;
|
||||
|
||||
/**
|
||||
* Class that contains a variety of utility methods for use with {@link PathPlotters}.
|
||||
*/
|
||||
class PathPlotters {
|
||||
|
||||
private PathPlotters() {}
|
||||
|
||||
/**
|
||||
* Converts the supplied vector into a lon/lat Point2D
|
||||
*/
|
||||
public static Point2D convert(UnwritableVectorIJK vector) {
|
||||
return convert(vector, new Point2D.Double());
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the supplied vector into a lon/lat Point2D
|
||||
*/
|
||||
public static Point2D convert(UnwritableVectorIJK vector, Point2D buffer) {
|
||||
LatitudinalVector latVec = CoordConverters.convertToLatitudinal(vector);
|
||||
buffer.setLocation(latVec.getLongitude(), latVec.getLatitude());
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotates a {@link Point2D} containing a latitude and longitude from one frame to another in
|
||||
* place.
|
||||
*
|
||||
* @param buffer the buffer containing the point prior to the rotation, and the rotated point
|
||||
* after the method finishes execution
|
||||
*
|
||||
* @param rotationalToNewFrame the matrix rotation from the frame in which buffer is initially
|
||||
* expressed to an alternate one
|
||||
*
|
||||
*/
|
||||
static void rotatePointInPlace(Point2D buffer, UnwritableRotationMatrixIJK rotationalToNewFrame) {
|
||||
UnwritableVectorIJK vector =
|
||||
CoordConverters.convert(new LatitudinalVector(1.0, buffer.getY(), buffer.getX()));
|
||||
VectorIJK rotated = rotationalToNewFrame.mxv(vector);
|
||||
LatitudinalVector latLonNew = CoordConverters.convertToLatitudinal(rotated);
|
||||
buffer.setLocation(latLonNew.getLongitude(), latLonNew.getLatitude());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Applies a rotation matrix to a path plotter to produce a new path plotter.
|
||||
*
|
||||
* @param plotter the plotter to rotate
|
||||
*
|
||||
* @param fromPlotterToNewFrame rotation from the supplied plotter's frame to a new frame; a copy
|
||||
* of this matrix is retained at construction time
|
||||
*
|
||||
* @return newly created, rotated {@link PathPlotter}
|
||||
*/
|
||||
public static PathPlotter rotate(final PathPlotter plotter,
|
||||
final UnwritableRotationMatrixIJK fromPlotterToNewFrame) {
|
||||
return new PathPlotter() {
|
||||
|
||||
private final UnwritableRotationMatrixIJK matrix =
|
||||
UnwritableRotationMatrixIJK.copyOf(fromPlotterToNewFrame);
|
||||
|
||||
@Override
|
||||
public Point2D plot(double p, Point2D buffer) {
|
||||
plotter.plot(p, buffer);
|
||||
rotatePointInPlace(buffer, matrix);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnwritableInterval getDomain() {
|
||||
return plotter.getDomain();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a rotation matrix to a closed path plotter to produce a new one.
|
||||
*
|
||||
* @param plotter the plotter to rotate
|
||||
*
|
||||
* @param fromPlotterToNewFrame rotation from the supplied plotter's frame to a new frame; a copy
|
||||
* of this matrix is retained at construction time
|
||||
*
|
||||
* @return newly created, rotated {@link PathPlotter}
|
||||
*/
|
||||
public static ClosedPathPlotter rotate(final ClosedPathPlotter plotter,
|
||||
final UnwritableRotationMatrixIJK fromPlotterToNewFrame) {
|
||||
|
||||
return new ClosedPathPlotter() {
|
||||
|
||||
private final UnwritableRotationMatrixIJK matrix =
|
||||
UnwritableRotationMatrixIJK.copyOf(fromPlotterToNewFrame);
|
||||
|
||||
@Override
|
||||
public Point2D plot(double p, Point2D buffer) {
|
||||
plotter.plot(p, buffer);
|
||||
rotatePointInPlace(buffer, matrix);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnwritableInterval getDomain() {
|
||||
return plotter.getDomain();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReferencePointInterior() {
|
||||
return plotter.isReferencePointInterior();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point2D getReferencePoint(Point2D buffer) {
|
||||
plotter.getReferencePoint(buffer);
|
||||
rotatePointInPlace(buffer, matrix);
|
||||
return buffer;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
140
src/main/java/picante/math/cones/PathSplitter.java
Normal file
140
src/main/java/picante/math/cones/PathSplitter.java
Normal file
@@ -0,0 +1,140 @@
|
||||
package picante.math.cones;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import java.awt.geom.PathIterator;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.locationtech.jts.geom.CoordinateSequence;
|
||||
import org.locationtech.jts.geom.impl.PackedCoordinateSequenceFactory;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.primitives.ImmutableDoubleArray;
|
||||
|
||||
/**
|
||||
* Converts a {@link PathIterator} into a list of JTS {@link CoordinateSequence}s.
|
||||
* <p>
|
||||
* Any duplicate or unnecessary elements of the path are filtered out (i.e. duplicate
|
||||
* {@link PathIterator#SEG_LINETO}).
|
||||
* </p>
|
||||
*/
|
||||
class PathSplitter implements Function<PathIterator, List<CoordinateSequence>> {
|
||||
|
||||
private final PackedCoordinateSequenceFactory coordSequenceFactory;
|
||||
|
||||
PathSplitter(PackedCoordinateSequenceFactory coordSequenceFactory) {
|
||||
this.coordSequenceFactory = checkNotNull(coordSequenceFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Breaks up the shape into a series of JTS {@link CoordinateSequence}s that represent individual
|
||||
* path components of the shape.
|
||||
*
|
||||
* @param shape the shape of interest
|
||||
*
|
||||
* @return a list of coordinate sequences in the order provided by the shape {@link PathIterator}
|
||||
*
|
||||
* @throws UnsupportedOperationException if any of the shape paths have with types of either:
|
||||
* {@link PathIterator#SEG_QUADTO} or {@link PathIterator#SEG_CUBICTO}
|
||||
*
|
||||
* @throws {@link IllegalArgumentException} if the supplied {@link PathIterator} does not start
|
||||
* with a segment type of {@link PathIterator#SEG_MOVETO}
|
||||
*/
|
||||
@Override
|
||||
public List<CoordinateSequence> apply(PathIterator iterator) {
|
||||
|
||||
ImmutableList.Builder<CoordinateSequence> resultBuilder = ImmutableList.builder();
|
||||
|
||||
double[] coords = new double[6];
|
||||
int type = Integer.MIN_VALUE;
|
||||
ImmutableDoubleArray.Builder accumulator = null;
|
||||
double[] start = new double[] {Double.NaN, Double.NaN};
|
||||
double[] previous = new double[] {Double.NaN, Double.NaN};
|
||||
|
||||
/*
|
||||
* Unfortunately this is a convoluted, somewhat by necessity in dealing with possibly invalid
|
||||
* PathIterator behaviors in a sane manner.
|
||||
*/
|
||||
for (; !iterator.isDone(); iterator.next()) {
|
||||
type = iterator.currentSegment(coords);
|
||||
|
||||
if ((accumulator == null) && (type != PathIterator.SEG_MOVETO)) {
|
||||
/*
|
||||
* Invalid path start, it must begin with a MOVETO.
|
||||
*/
|
||||
throw new IllegalArgumentException(
|
||||
"Supplied path iterator does not start with a PathIterator.SEG_MOVETO type.");
|
||||
}
|
||||
|
||||
/*
|
||||
* Only proceed if the type is CLOSE or coords does not match previous.
|
||||
*/
|
||||
if (type == PathIterator.SEG_CLOSE || previous[0] != coords[0] || previous[1] != coords[1]) {
|
||||
|
||||
/*
|
||||
* MOVETO indicates that a new segment is starting.
|
||||
*/
|
||||
if (type == PathIterator.SEG_MOVETO) {
|
||||
|
||||
/*
|
||||
* Accumulator is only null on the first pass through the loop.
|
||||
*/
|
||||
if (accumulator != null) {
|
||||
resultBuilder.add(coordSequenceFactory.create(accumulator.build().toArray(), 2));
|
||||
}
|
||||
|
||||
accumulator = ImmutableDoubleArray.builder();
|
||||
accumulator.add(coords[0]);
|
||||
accumulator.add(coords[1]);
|
||||
|
||||
/*
|
||||
* Cache the starting point of the current segment for use when CLOSE happens.
|
||||
*/
|
||||
System.arraycopy(coords, 0, start, 0, 2);
|
||||
System.arraycopy(coords, 0, previous, 0, 2);
|
||||
|
||||
} else if (type == PathIterator.SEG_CLOSE) {
|
||||
|
||||
/*
|
||||
* Check to see if we need to add the closing point to the accumulator. This check is done
|
||||
* just to prevent duplicating a coordinate that may already be present in the output
|
||||
* sequence. Compare with previous, as coords is not necessarily populated when type is
|
||||
* SEG_CLOSE.
|
||||
*/
|
||||
if (previous[0] != start[0] || previous[1] != start[1]) {
|
||||
accumulator.add(start[0]);
|
||||
accumulator.add(start[1]);
|
||||
}
|
||||
|
||||
} else if (type == PathIterator.SEG_LINETO) {
|
||||
|
||||
/*
|
||||
* Accumulate the next point along the linear boundary only if it's not equal to the
|
||||
* previous point.
|
||||
*/
|
||||
accumulator.add(coords[0]);
|
||||
accumulator.add(coords[1]);
|
||||
System.arraycopy(coords, 0, previous, 0, 2);
|
||||
|
||||
|
||||
} else {
|
||||
throw new UnsupportedOperationException(
|
||||
"Unable to convert shape, only linear boundaries are supported."
|
||||
+ " Consider flattening the shape first.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Add the last path segment to the resultBuilder.
|
||||
*/
|
||||
resultBuilder.add(coordSequenceFactory.create(accumulator.build().toArray(), 2));
|
||||
|
||||
return resultBuilder.build();
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
75
src/main/java/picante/math/cones/PolygonalCone.java
Normal file
75
src/main/java/picante/math/cones/PolygonalCone.java
Normal file
@@ -0,0 +1,75 @@
|
||||
package picante.math.cones;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import picante.math.intervals.UnwritableInterval;
|
||||
import picante.math.vectorspace.UnwritableVectorIJK;
|
||||
import picante.math.vectorspace.VectorIJK;
|
||||
|
||||
/**
|
||||
* A Polygonal Cone. Parameterize from 0-n in order to generate the n sides, in that order.
|
||||
* getEdge(0) will return corner 0, getEdge(1) will return corner 1...
|
||||
*
|
||||
* TODO This needs testing. I have not had the chance to perform integration tests.
|
||||
*
|
||||
* @author poffert1
|
||||
*
|
||||
*/
|
||||
|
||||
public class PolygonalCone implements Cone {
|
||||
private final UnwritableVectorIJK vertex;
|
||||
private final UnwritableVectorIJK interiorPoint;
|
||||
private final int n;
|
||||
private final List<UnwritableVectorIJK> corners;
|
||||
|
||||
|
||||
PolygonalCone(UnwritableVectorIJK vertex, UnwritableVectorIJK interiorPt,
|
||||
List<UnwritableVectorIJK> pts3d) {
|
||||
this.vertex = vertex;
|
||||
this.interiorPoint = interiorPt;
|
||||
this.n = pts3d.size();
|
||||
|
||||
List<UnwritableVectorIJK> corners = new ArrayList<>(pts3d);
|
||||
corners.add(corners.get(0));
|
||||
corners.add(corners.get(1)); // I am adding two vectors to the end. This is to handle the case
|
||||
// where the end of the domain is entered (4)
|
||||
this.corners = corners;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnwritableVectorIJK getVertex() {
|
||||
return this.vertex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnwritableVectorIJK getEdge(double parameter) {
|
||||
int idx0 = (int) parameter;
|
||||
int idx1 = idx0 + 1;
|
||||
double amt = parameter - idx0;
|
||||
|
||||
UnwritableVectorIJK vector0 = corners.get(idx0);
|
||||
UnwritableVectorIJK vector1 = corners.get(idx1);
|
||||
|
||||
return VectorIJK.combine(1 - amt, vector0, amt, vector1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnwritableInterval getParameterDomain() {
|
||||
return new UnwritableInterval(0, n);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnwritableVectorIJK getInteriorPoint() {
|
||||
return this.interiorPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only return the original list of n corners
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public List<UnwritableVectorIJK> getCorners() {
|
||||
return corners.subList(0, n);
|
||||
}
|
||||
|
||||
}
|
||||
173
src/main/java/picante/math/cones/Projection.java
Normal file
173
src/main/java/picante/math/cones/Projection.java
Normal file
@@ -0,0 +1,173 @@
|
||||
package picante.math.cones;
|
||||
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Shape;
|
||||
import java.awt.geom.Path2D;
|
||||
import java.awt.geom.Point2D;
|
||||
import picante.math.vectorspace.UnwritableRotationMatrixIJK;
|
||||
|
||||
/**
|
||||
*
|
||||
* Interface that describes an invertible map projection that takes a location on a body expressed
|
||||
* in latitude and longitude and converts it to the 2D projection coordinate space. The interface
|
||||
* also provides the inverse.
|
||||
* <p>
|
||||
* This API "abuses" the {@link Point2D} class as a vehicle for carrying longitude (x
|
||||
* component) and latitude (y component). This was done intentionally to stick with the utilization
|
||||
* of the {@link Graphics} classes for rendering.
|
||||
* </p>
|
||||
*
|
||||
* An interface that provides methods designed to work with open and closed parameterized paths in
|
||||
* longitude and latitude into map projected space. The {@link PathPlotter} interface is used by
|
||||
* these implementations in a specific way, as it is considered to be providing a curve
|
||||
* parameterized in the longitude-latitude angle space.
|
||||
* <p>
|
||||
* The reason the {@link PathPlotter} interface is utilized here instead of a collection of discrete
|
||||
* sampled points, is that this allows the map projection to decide to refine the step-size in the
|
||||
* event that projection artifacts require an increased sampling in a region. If you are working
|
||||
* with a discrete set of points, then take a look at
|
||||
* {@link PathPlotters#create(crucible.core.data.list.indexable.Indexable)}.
|
||||
* </p>
|
||||
* <p>
|
||||
* Path plotters consumed by the implementations of this interface are expected to supply longitude
|
||||
* as the x-coordinate of the point, and latitude as the y-coordinate of the point. Further, they
|
||||
* may assume that longitudes must be confined to the range [-Pi,Pi], and latitudes should be
|
||||
* confined to the range: [-Pi/2, Pi/2].
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
interface Projection {
|
||||
|
||||
/**
|
||||
* Projects a supplied longitude and latitude into the map projected space.
|
||||
* <p>
|
||||
* Implementors may rely upon longitude lying in the range: [-Math.PI, Math.PI] and latitude in
|
||||
* the range: [-Math.PI/2.0, Math.PI/2.0]
|
||||
* </p>
|
||||
*
|
||||
* @param latLon longitude and latitude in radians
|
||||
* @param buffer buffer to receive the results of the projection
|
||||
*
|
||||
* @return a reference to the supplied buffer for convenience in chaining method calls
|
||||
*/
|
||||
public <P extends Point2D> P project(Point2D latLon, P buffer);
|
||||
|
||||
default Point2D project(Point2D latLon) {
|
||||
return project(latLon, new Point2D.Double());
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a point expressed in the projection and inverts it back into longitude and latitude.
|
||||
*
|
||||
* @param mapLocation the location in the map projected coordinates
|
||||
* @param buffer buffer to receive the results of the inversion in longitude latitude coordinates.
|
||||
*
|
||||
* @return a reference to the supplied buffer for convenience in chaining method calls
|
||||
*
|
||||
* @throws InversionFailedException if the supplied mapLocation is unable to be inverted,
|
||||
* typically because it lies outside {@link #getValidRegion()}.
|
||||
*/
|
||||
public <P extends Point2D> P invert(Point2D mapLocation, P buffer);
|
||||
|
||||
default Point2D invert(Point2D mapLocation) {
|
||||
return invert(mapLocation, new Point2D.Double());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the "reference" point of the projection, i.e. the point at which the map
|
||||
* projection distortion is a minimum. Generally this is the central point of the projection, but
|
||||
* it may not be the case always.
|
||||
*
|
||||
* @return a point in normalized coordinates; it may lie outside the [0,0]x[1,1] bounding box.
|
||||
* This should be a newly created instance of Point2D every time this method is invoked
|
||||
* for safety reasons.
|
||||
*/
|
||||
public Point2D getReference();
|
||||
|
||||
/**
|
||||
* Returns the native aspect ratio of the projected space.
|
||||
* <p>
|
||||
* Since the projected space is a (0,1) x (0,1) normalized space, this method returns the ratio of
|
||||
* the width to the height.
|
||||
* </p>
|
||||
*
|
||||
* @return the number of width units divided by the number of equivalent height units.
|
||||
*/
|
||||
public double getNativeAspectRatio();
|
||||
|
||||
/**
|
||||
* Returns the subset of the (0,1) x (0,1) normalized space that where the implementation will
|
||||
* project into.
|
||||
* <p>
|
||||
* Some projections, due to their nature, will not fill the normalized space entirely. This method
|
||||
* will give the boundary and interior of that region.
|
||||
* </p>
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Shape getValidRegion();
|
||||
|
||||
/**
|
||||
* Projects an open path specified in longitude and latitude into the map projected space.
|
||||
*
|
||||
* @param plotter the path plotter providing the parameterized function generating longitude and
|
||||
* latitude of a path on the surface.
|
||||
* @param step the requested "step-size" used to generate points.
|
||||
*
|
||||
* @return a reference to buffer, whose contents capture the open path projected into the map
|
||||
* projected space. For map projections with branch cuts and other features, this may
|
||||
* require multiple disconnected paths to properly capture.
|
||||
*
|
||||
* @throws IllegalArgumentException if step is not strictly positive
|
||||
*/
|
||||
public <R extends Path2D> R projectOpenPath(PathPlotter plotter, double step, R buffer);
|
||||
|
||||
default Path2D projectOpenPath(PathPlotter plotter, double step) {
|
||||
return projectOpenPath(plotter, step, new Path2D.Double());
|
||||
}
|
||||
|
||||
/**
|
||||
* Projects a closed path specified in longitude and latitude into the map projected space.
|
||||
*
|
||||
* @param plotter the path plotter providing the parameterized function generating the longitude
|
||||
* and latitude of a closed path on the surface.
|
||||
* @param step the requested "step-size" used to generate points.
|
||||
* @param buffer the Path2D to populate, any previously defined path content is dropped via a call
|
||||
* to {@link Path2D#reset()} prior to populating additional points.
|
||||
*
|
||||
* @return a reference to buffer, whose contents capture the closed path projected into the map
|
||||
* projected space. For map projections with branch cuts and other features, this may
|
||||
* require multiple, disconnected areas.
|
||||
*
|
||||
* @throws IllegalArgumentException if step is not strictly positive
|
||||
*/
|
||||
public <R extends Path2D> R projectClosedPath(ClosedPathPlotter plotter, double step, R buffer);
|
||||
|
||||
default Path2D projectClosedPath(ClosedPathPlotter plotter, double step) {
|
||||
return projectClosedPath(plotter, step, new Path2D.Double());
|
||||
}
|
||||
|
||||
/**
|
||||
* Certain classes of projections are derivatives (i.e. flipped, clipped, or rotated) of other
|
||||
* projections. This method returns the type of the original or "source" projection,
|
||||
* walking the hierarchy recursively if required.
|
||||
* <p>
|
||||
* Most implementations will simply return {@link Projection#getClass()}.
|
||||
* </p>
|
||||
*
|
||||
* @return the type of the original projection
|
||||
*/
|
||||
Class<? extends Projection> getOriginalClass();
|
||||
|
||||
/**
|
||||
* Creates a new projection, wrapping the instance, where vectors derived from latitude/longitude
|
||||
* pairs are rotated prior to application of the projection.
|
||||
*
|
||||
* @param rotation rotation from standard latitude/longitude, body-fixed coordinate system to the
|
||||
* newly defined one.
|
||||
*
|
||||
* @return new instance of the projection
|
||||
*/
|
||||
Projection rotate(UnwritableRotationMatrixIJK rotation);
|
||||
|
||||
}
|
||||
94
src/main/java/picante/math/cones/ProjectionBuilders.java
Normal file
94
src/main/java/picante/math/cones/ProjectionBuilders.java
Normal file
@@ -0,0 +1,94 @@
|
||||
package picante.math.cones;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static picante.units.FundamentalPhysicalConstants.TWOPI;
|
||||
import picante.designpatterns.Builder;
|
||||
|
||||
class ProjectionBuilders {
|
||||
|
||||
private ProjectionBuilders() {}
|
||||
|
||||
/**
|
||||
* Creates a simple cylindrical projection that covers the entire body in longitude.
|
||||
*
|
||||
* <pre>
|
||||
* +========================================================+
|
||||
* ----|---+------------------------------------------------+---|---- Math.PI/2.0
|
||||
* | | | |
|
||||
* +========================================================+
|
||||
* |---| |
|
||||
* ^ | +---------------------|------x | +---
|
||||
* | | | | i+1 | |
|
||||
* overPole | | |--- splitTolerance --| | |
|
||||
* BorderPad | | on other | |
|
||||
* | x branch | x
|
||||
* | i |
|
||||
* --------+------------------------------------------------+-------- -Math.PI/2.0
|
||||
*
|
||||
* lowerBranchValue lowerBranchValue + (2 * Math.PI)
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* When the difference between two successive points in a {@link PathPlotter} exceeds the
|
||||
* splitTolerance then the map projection will consider them to be connected across the branch
|
||||
* cut. Typically this value should be something slightly larger than half the difference between
|
||||
* the lowerBranchValue and upperBranchValue for the projection to work properly.
|
||||
* </p>
|
||||
* <p>
|
||||
* Note: the current implementation does not validate or otherwise alter the supplied latitude (y
|
||||
* coordinate) values. These are unnecessary to manipulate in the projection. It does support
|
||||
* paths that pass through the pole or that contain it, so long as they are simply connected.
|
||||
* Paths that are not simply connected may work or may not. Further, each closed region should lie
|
||||
* in a single hemisphere.
|
||||
* </p>
|
||||
*
|
||||
* @param lowerBranchValue the value representing the lower longitude of the branch cut in radians
|
||||
* @param splitTolerance the value of the splitTolerance expressed in radians
|
||||
* @param overPoleBorderPad the minimum amount of "closure" padding added to paths that
|
||||
* enclose either pole specified in radians.
|
||||
*
|
||||
* @return the newly constructed map projection
|
||||
*
|
||||
* @throws IllegalArgumentException if lowerBranchLongitude lies outside the range [-2.0*Math.PI,
|
||||
* 0.0]
|
||||
*/
|
||||
static class SimpleCylindrical implements Builder<Projection, RuntimeException> {
|
||||
|
||||
private double lowerBranchLongitude = -Math.PI;
|
||||
private double splitTolerance = Math.PI - 0.001;
|
||||
private double overPoleBorderPad = Math.toRadians(20.0);
|
||||
|
||||
private SimpleCylindrical() {}
|
||||
|
||||
public SimpleCylindrical withLowerBranch(double longitudeInRadians) {
|
||||
checkArgument(longitudeInRadians >= -TWOPI);
|
||||
checkArgument(longitudeInRadians <= 0.0);
|
||||
this.lowerBranchLongitude = longitudeInRadians;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SimpleCylindrical withSplit(double toleranceInRadians) {
|
||||
checkArgument(toleranceInRadians > 0);
|
||||
checkArgument(toleranceInRadians < TWOPI);
|
||||
this.splitTolerance = toleranceInRadians;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SimpleCylindrical withOverPoleBorder(double paddingInRadians) {
|
||||
checkArgument(overPoleBorderPad > 0);
|
||||
this.overPoleBorderPad = paddingInRadians;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Projection build() throws RuntimeException {
|
||||
return new picante.math.cones.SimpleCylindrical(lowerBranchLongitude, splitTolerance, overPoleBorderPad);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static SimpleCylindrical simpleCylindrical() {
|
||||
return new SimpleCylindrical();
|
||||
}
|
||||
|
||||
}
|
||||
102
src/main/java/picante/math/cones/RotatedProjection.java
Normal file
102
src/main/java/picante/math/cones/RotatedProjection.java
Normal file
@@ -0,0 +1,102 @@
|
||||
package picante.math.cones;
|
||||
|
||||
import java.awt.Shape;
|
||||
import java.awt.geom.Path2D;
|
||||
import java.awt.geom.Point2D;
|
||||
import picante.math.coords.CoordConverters;
|
||||
import picante.math.coords.LatitudinalVector;
|
||||
import picante.math.vectorspace.UnwritableRotationMatrixIJK;
|
||||
import picante.math.vectorspace.UnwritableVectorIJK;
|
||||
|
||||
class RotatedProjection extends DerivativeProjection {
|
||||
|
||||
public RotatedProjection(Projection shapeProjection, UnwritableRotationMatrixIJK rotMat) {
|
||||
super(shapeProjection);
|
||||
this.shapeProjection = shapeProjection;
|
||||
this.rotMat = UnwritableRotationMatrixIJK.copyOf(rotMat);
|
||||
}
|
||||
|
||||
|
||||
private final Projection shapeProjection;
|
||||
private final UnwritableRotationMatrixIJK rotMat;
|
||||
|
||||
|
||||
@Override
|
||||
public <P extends Point2D> P project(Point2D latLon, P buffer) {
|
||||
/*
|
||||
* Rotate to, then project
|
||||
*/
|
||||
Point2D rotatedLatLon = rotateTo(latLon);
|
||||
shapeProjection.project(rotatedLatLon, buffer);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <P extends Point2D> P invert(Point2D mapLocation, P buffer) {
|
||||
|
||||
/*
|
||||
* Project, then rotate from
|
||||
*/
|
||||
shapeProjection.invert(mapLocation, buffer);
|
||||
buffer.setLocation(rotateFrom(buffer));
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getNativeAspectRatio() {
|
||||
return shapeProjection.getNativeAspectRatio();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape getValidRegion() {
|
||||
return shapeProjection.getValidRegion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <R extends Path2D> R projectOpenPath(PathPlotter plotter, double step, R buffer) {
|
||||
/*
|
||||
* Rotate to, then project
|
||||
*/
|
||||
PathPlotter rotatedPlotter = PathPlotters.rotate(plotter, rotMat);
|
||||
return shapeProjection.projectOpenPath(rotatedPlotter, step, buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <R extends Path2D> R projectClosedPath(ClosedPathPlotter plotter, double step, R buffer) {
|
||||
/*
|
||||
* Rotate to, then project
|
||||
*/
|
||||
ClosedPathPlotter rotatedPlotter = PathPlotters.rotate(plotter, rotMat);
|
||||
return shapeProjection.projectClosedPath(rotatedPlotter, step, buffer);
|
||||
}
|
||||
|
||||
private Point2D rotateTo(Point2D latLon) {
|
||||
UnwritableVectorIJK vector =
|
||||
CoordConverters.convert(new LatitudinalVector(1.0, latLon.getY(), latLon.getX()));
|
||||
UnwritableVectorIJK rotatedVector = rotMat.mxv(vector);
|
||||
LatitudinalVector rotatedLatVector = CoordConverters.convertToLatitudinal(rotatedVector);
|
||||
return new Point2D.Double(rotatedLatVector.getLongitude(), rotatedLatVector.getLatitude());
|
||||
}
|
||||
|
||||
private Point2D rotateFrom(Point2D latLon) {
|
||||
UnwritableVectorIJK vector =
|
||||
CoordConverters.convert(new LatitudinalVector(1.0, latLon.getY(), latLon.getX()));
|
||||
UnwritableVectorIJK rotatedVector = rotMat.mtxv(vector);
|
||||
LatitudinalVector rotatedLatVector = CoordConverters.convertToLatitudinal(rotatedVector);
|
||||
return new Point2D.Double(rotatedLatVector.getLongitude(), rotatedLatVector.getLatitude());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point2D getReference() {
|
||||
/*
|
||||
* Return the parent shape projection reference point. This is because the operation here is
|
||||
* rotate, then project. So the projection is the same, it is merely centered on a different
|
||||
* point.
|
||||
*/
|
||||
return shapeProjection.getReference();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
89
src/main/java/picante/math/cones/SequencesConverter.java
Normal file
89
src/main/java/picante/math/cones/SequencesConverter.java
Normal file
@@ -0,0 +1,89 @@
|
||||
package picante.math.cones;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.locationtech.jts.geom.Coordinate;
|
||||
import org.locationtech.jts.geom.CoordinateSequence;
|
||||
import org.locationtech.jts.geom.Geometry;
|
||||
import org.locationtech.jts.geom.GeometryFactory;
|
||||
import org.locationtech.jts.geom.LineString;
|
||||
import org.locationtech.jts.geom.LinearRing;
|
||||
import org.locationtech.jts.geom.Point;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
/**
|
||||
* Function that converts a list of {@link CoordinateSequence}s into a list of {@link Geometry}s.
|
||||
* <p>
|
||||
* <ul>
|
||||
* <li>Sequences with a single point are converted to {@link Point}</li>
|
||||
* <li>Sequences with 4 or more entries, where first and last match exactly are converted to
|
||||
* {@link LinearRing}</li>
|
||||
* <li>All other sequences are converted to {@link LineString}</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*/
|
||||
class SequencesConverter implements Function<List<CoordinateSequence>, List<Geometry>> {
|
||||
|
||||
private final GeometryFactory geometryFactory;
|
||||
|
||||
SequencesConverter(GeometryFactory geometryFactory) {
|
||||
this.geometryFactory = checkNotNull(geometryFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Geometry> apply(List<CoordinateSequence> sequences) {
|
||||
|
||||
/*
|
||||
* Convert the coordinate sequences to Points, LineStrings, or Polygons depending on whether
|
||||
* they are closed on themselves and have more than one entry.
|
||||
*/
|
||||
ImmutableList.Builder<Geometry> geometries =
|
||||
ImmutableList.builderWithExpectedSize(sequences.size());
|
||||
|
||||
Coordinate first = new Coordinate();
|
||||
Coordinate last = new Coordinate();
|
||||
|
||||
for (CoordinateSequence sequence : sequences) {
|
||||
if (sequence.size() == 1) {
|
||||
geometries.add(geometryFactory.createPoint(sequence));
|
||||
} else if (isLinearRing(sequence, first, last)) {
|
||||
geometries.add(geometryFactory.createLinearRing(sequence));
|
||||
} else {
|
||||
geometries.add(geometryFactory.createLineString(sequence));
|
||||
}
|
||||
}
|
||||
|
||||
return geometries.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the sequence of coordinates should be a linear ring.
|
||||
* <p>
|
||||
* Note: this method does <b>NOT</b> validate the contents of the ring, it just simply checks that
|
||||
* the first and last entry in the supplied sequence match and that there are at least 4 entries.
|
||||
* </p>
|
||||
*
|
||||
* @param sequence candidate sequence of coordinates
|
||||
* @param bufferA a coordinate buffer to use for retrieval from sequence
|
||||
* @param bufferB a coordinate buffer to use for retrieval from sequence
|
||||
*
|
||||
* @return true if sequence might be a linear ring, false otherwise
|
||||
*/
|
||||
boolean isLinearRing(CoordinateSequence sequence, Coordinate bufferA, Coordinate bufferB) {
|
||||
|
||||
if (sequence.size() < 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sequence.getCoordinate(0, bufferA);
|
||||
sequence.getCoordinate(sequence.size() - 1, bufferB);
|
||||
|
||||
return bufferA.equals(bufferB);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
490
src/main/java/picante/math/cones/SimpleCylindrical.java
Normal file
490
src/main/java/picante/math/cones/SimpleCylindrical.java
Normal file
@@ -0,0 +1,490 @@
|
||||
package picante.math.cones;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static picante.units.FundamentalPhysicalConstants.TWOPI;
|
||||
import java.awt.Shape;
|
||||
import java.awt.geom.Area;
|
||||
import java.awt.geom.Path2D;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import picante.exceptions.BugException;
|
||||
import picante.math.intervals.UnwritableInterval;
|
||||
|
||||
/**
|
||||
* Simple "full sphere" cylindrical projection implementation.
|
||||
* <p>
|
||||
* This class is public to enable identification of its projection type by standard code
|
||||
* introspection techniques, however it can only be created through the static methods implemented
|
||||
* on the {@link Projections} class.
|
||||
* </p>
|
||||
* <p>
|
||||
* This code is derived from a slightly more generic implementation of this algorithm, so there are
|
||||
* artifacts with regards to fields on the class that could be implementation constants. It was left
|
||||
* this way so that the more general implementation could be easily extracted if necessary in the
|
||||
* future.
|
||||
* </p>
|
||||
* <p>
|
||||
* TODO: Handle the multiple "wrapping" case. The current code when it unwinds a closed path does
|
||||
* not properly allow it to be rendered unless it only moves left or right across the branch cut
|
||||
* once.
|
||||
* </p>
|
||||
*/
|
||||
class SimpleCylindrical extends AbstractProjection {
|
||||
|
||||
/**
|
||||
* The value of the left branch in radians. This is necessary since this code is not only
|
||||
* unraveling the branch cut wrapping, but performing the affine transformation from radians space
|
||||
* to the normalized space.
|
||||
*/
|
||||
private final double branchLeftInRadians;
|
||||
|
||||
/**
|
||||
* The numeric value at which the left branch is defined. Note: in this implementation this value
|
||||
* is always 0.0.
|
||||
*/
|
||||
private final double branchLeft;
|
||||
|
||||
/**
|
||||
* The numeric value at which the right branch is defined. Note: in this implementation this value
|
||||
* is always 1.0.
|
||||
*/
|
||||
private final double branchRight;
|
||||
|
||||
/**
|
||||
* The value at which a step in a point plotter must exceed to be considered a branch crossing.
|
||||
* This value should be something approximately 0.5 in this implementation for most applications.
|
||||
*/
|
||||
private final double split;
|
||||
|
||||
/**
|
||||
* The mid-point between the two branches, normally would be the average of branchLeft and
|
||||
* branchRight which in this implementation is 0.5.
|
||||
*/
|
||||
private final double midPoint;
|
||||
|
||||
/**
|
||||
* The amount to shift by numerically when moving a point from one branch to another towards the
|
||||
* right branch. In this implementation it is 1.0 (branchRight - branchLeft).
|
||||
*/
|
||||
private final double branchRightShift;
|
||||
|
||||
/**
|
||||
* The amount of horizontal (longitude) separation required between the first and final point of a
|
||||
* closed path to be considered "open" and thus enclosing the pole.
|
||||
*/
|
||||
private final double overPoleBranchTestTolerance = (TWOPI - 0.01) / TWOPI;
|
||||
|
||||
/**
|
||||
* The amount of padding to add to tracks that enclose the pole.
|
||||
*/
|
||||
private final double overPoleBorderPadding;
|
||||
|
||||
/**
|
||||
* Package private constructor.
|
||||
*
|
||||
* @param branchLeftInRadians
|
||||
* @param split
|
||||
*/
|
||||
SimpleCylindrical(double branchLeftInRadians, double split, double overPoleBorderPadding) {
|
||||
super();
|
||||
|
||||
checkArgument(branchLeftInRadians >= -TWOPI);
|
||||
checkArgument(branchLeftInRadians <= 0.0);
|
||||
|
||||
checkArgument(split > 0);
|
||||
checkArgument(split < TWOPI);
|
||||
|
||||
checkArgument(overPoleBorderPadding > 0);
|
||||
|
||||
this.branchLeftInRadians = branchLeftInRadians;
|
||||
this.branchLeft = 0.0;
|
||||
this.branchRight = 1.0;
|
||||
this.split = split / TWOPI;
|
||||
this.midPoint = 0.5;
|
||||
this.branchRightShift = branchRight - branchLeft;
|
||||
this.overPoleBorderPadding = overPoleBorderPadding / TWOPI;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <P extends Point2D> P project(Point2D latLon, P buffer) {
|
||||
|
||||
/*
|
||||
* Assuming the latitude and longitude lie in the appropriate ranges:
|
||||
*/
|
||||
double y = -1.0 / Math.PI * latLon.getY() + 0.5;
|
||||
|
||||
double longitude = latLon.getX() - branchLeftInRadians;
|
||||
|
||||
if (longitude < 0) {
|
||||
longitude += TWOPI;
|
||||
} else if (longitude > TWOPI) {
|
||||
longitude -= TWOPI;
|
||||
}
|
||||
|
||||
double x = longitude / TWOPI;
|
||||
|
||||
buffer.setLocation(x, y);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <P extends Point2D> P invert(Point2D mapLocation, P buffer) {
|
||||
|
||||
double latitude = -Math.PI * (mapLocation.getY() - 0.5);
|
||||
|
||||
double longitude = mapLocation.getX() * TWOPI + branchLeftInRadians;
|
||||
|
||||
if (longitude < -Math.PI) {
|
||||
longitude += TWOPI;
|
||||
} else if (longitude > TWOPI) {
|
||||
longitude -= TWOPI;
|
||||
}
|
||||
|
||||
buffer.setLocation(longitude, latitude);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method exists as the original code did not also perform the affine transformation scaling
|
||||
* the latitude and longitude to the normalized space.
|
||||
*/
|
||||
private PathPlotter transformToNormalizedCoordinates(final PathPlotter plotter) {
|
||||
return new PathPlotter() {
|
||||
|
||||
@Override
|
||||
public Point2D plot(double p, Point2D buffer) {
|
||||
|
||||
/*
|
||||
* Plot the latLon based point.
|
||||
*/
|
||||
plotter.plot(p, buffer);
|
||||
|
||||
/*
|
||||
* Convert the point to the appropriate range.
|
||||
*/
|
||||
project(buffer, buffer);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnwritableInterval getDomain() {
|
||||
return plotter.getDomain();
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public <R extends Path2D> R projectOpenPath(PathPlotter plotter, double step, R buffer) {
|
||||
|
||||
/*
|
||||
* This code could utilize the createSingleBranchPath method utilized in the projectClosedPath
|
||||
* method in much the same manner. However, this would result in lots of redundant points along
|
||||
* the path. So this method skips those points in the way it is implemented.
|
||||
*/
|
||||
|
||||
checkArgument(step > 0, "Step argument must be greater than zero.");
|
||||
|
||||
/*
|
||||
* Reset the supplied buffer to dump all of the coordinates and segments it possesses.
|
||||
*/
|
||||
buffer.reset();
|
||||
|
||||
/*
|
||||
* Adapt the supplied plotter into normalized coordinates before wrapping.
|
||||
*/
|
||||
plotter = transformToNormalizedCoordinates(plotter);
|
||||
|
||||
/*
|
||||
* Consider returning multiple paths, that cover both edges of the branch cut.
|
||||
*/
|
||||
Point2D.Double prevPoint = new Point2D.Double();
|
||||
Point2D.Double point = new Point2D.Double();
|
||||
|
||||
/*
|
||||
* Setup the iteration of the point plotter. Start at the origin of the specified domain.
|
||||
*/
|
||||
double t = plotter.getDomain().getBegin();
|
||||
plotter.plot(t, point);
|
||||
|
||||
/*
|
||||
* Take the first "step", contract to the end of the domain if it oversteps.
|
||||
*/
|
||||
int i = 1;
|
||||
t = Math.min(plotter.getDomain().getBegin() + step, plotter.getDomain().getEnd());
|
||||
|
||||
/*
|
||||
* Check to see if the starting point is exactly one of the poles. If it is then peek ahead to
|
||||
* the next point. We will set the longitude of the point (which is meaningless in general,
|
||||
* except in this projection) to match that value.
|
||||
*/
|
||||
if ((point.y == 0.0) || (point.y == 1.0)) {
|
||||
plotter.plot(t, prevPoint);
|
||||
point.x = prevPoint.x;
|
||||
}
|
||||
|
||||
/*
|
||||
* Move to the start of the path, and set the previous point to the path starting location.
|
||||
*/
|
||||
buffer.moveTo(point.x, point.y);
|
||||
prevPoint.setLocation(point);
|
||||
|
||||
while (t <= plotter.getDomain().getEnd()) {
|
||||
|
||||
plotter.plot(t, point);
|
||||
|
||||
/*
|
||||
* Check to see if point is the pole. If it is then adjust the longitude to match that of the
|
||||
* previous point.
|
||||
*/
|
||||
if ((point.y == 0.0) || (point.y == 1.0)) {
|
||||
point.x = prevPoint.x;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check to see if the separation between this point and the next exceeds split.
|
||||
*/
|
||||
if (Math.abs(point.x - prevPoint.x) > split) {
|
||||
|
||||
/*
|
||||
* If point.x is closer to branchLeft, then adjust it to the right.
|
||||
*/
|
||||
if (point.x < midPoint) {
|
||||
buffer.lineTo(point.x - branchLeft + branchRight, point.y);
|
||||
buffer.moveTo(branchLeft - branchRight + prevPoint.x, prevPoint.y);
|
||||
buffer.lineTo(point.x, point.y);
|
||||
|
||||
} else {
|
||||
buffer.lineTo(branchLeft - branchRight + point.x, point.y);
|
||||
buffer.moveTo(prevPoint.x - branchLeft + branchRight, prevPoint.y);
|
||||
buffer.lineTo(point.x, point.y);
|
||||
}
|
||||
|
||||
} else {
|
||||
buffer.lineTo(point.x, point.y);
|
||||
}
|
||||
|
||||
/*
|
||||
* Prepare to enter the next iteration. First check to see if we are on the final point of the
|
||||
* iteration. If we are, then simply return the path we've been creating. Otherwise continue
|
||||
* on.
|
||||
*/
|
||||
if (t == plotter.getDomain().getEnd()) {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/*
|
||||
* Update prevPoint, i, and t for the next iteration.
|
||||
*/
|
||||
prevPoint.setLocation(point);
|
||||
i++;
|
||||
t = Math.min(plotter.getDomain().getBegin() + i * step, plotter.getDomain().getEnd());
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* We should never reach here if the iteration through the steps works properly.
|
||||
*/
|
||||
throw new BugException("Failure in projecting open path. This should never happen and "
|
||||
+ "is indicative of a failure in the projection algorithm.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public <R extends Path2D> R projectClosedPath(ClosedPathPlotter plotter, double step, R buffer) {
|
||||
|
||||
|
||||
checkArgument(step > 0, "Step argument must be greater than zero.");
|
||||
|
||||
/*
|
||||
* Create a boundary path on a single branch. If the closed path contains the pole, then the
|
||||
* path will start and end approximately 2*PI apart.
|
||||
*/
|
||||
createSingleBranchPath(plotter, plotter.getDomain(), step, buffer);
|
||||
|
||||
Point2D end = buffer.getCurrentPoint();
|
||||
Point2D start = getPathStart(buffer);
|
||||
|
||||
/*
|
||||
* Won't work for paths that cross more than one branch in the same direction (spiral around the
|
||||
* sphere)
|
||||
*/
|
||||
SimpleCylindricalPathType
|
||||
.identifyType(buffer, start, end, overPoleBranchTestTolerance, branchLeft, branchRight)
|
||||
.close(buffer, start, end, branchRightShift, overPoleBorderPadding);
|
||||
/*
|
||||
* Test if the reference point is an interior point, by projecting it
|
||||
*/
|
||||
boolean isReferencePointInterior = plotter.isReferencePointInterior();
|
||||
Point2D referencePt = plotter.getReferencePoint();
|
||||
Point2D projectedReferencePt = project(referencePt, new Point2D.Double());
|
||||
/*
|
||||
* Render the inside of the buffer if the reference point is contained and is interior, or if it
|
||||
* is not contained and it is not interior.
|
||||
*/
|
||||
if (buffer.contains(projectedReferencePt) == isReferencePointInterior) {
|
||||
/*
|
||||
* This is an abuse of the accumulator implementation, but it's more efficient than performing
|
||||
* the clone under the build() method.
|
||||
*/
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
||||
Rectangle2D.Double entireShape = new Rectangle2D.Double(-overPoleBorderPadding,
|
||||
-2 * overPoleBorderPadding, 1 + 2 * overPoleBorderPadding, 1 + 4 * overPoleBorderPadding);
|
||||
|
||||
Area entireShapeArea = new Area(entireShape);
|
||||
entireShapeArea.subtract(new Area(buffer));
|
||||
|
||||
buffer.reset();
|
||||
buffer.append(entireShapeArea, false);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link Path2D} from the supplied plotter in a simple cylindrical projection across
|
||||
* multiple branches as necessary.
|
||||
*/
|
||||
private <R extends Path2D> R createSingleBranchPath(PathPlotter plotter,
|
||||
UnwritableInterval domain, double step, R buffer) {
|
||||
|
||||
/*
|
||||
* Dump the contents of buffer.
|
||||
*/
|
||||
buffer.reset();
|
||||
|
||||
/*
|
||||
* Adapt the supplied plotter into normalized coordinates before wrapping.
|
||||
*/
|
||||
plotter = transformToNormalizedCoordinates(plotter);
|
||||
|
||||
checkArgument(!domain.isSingleton(),
|
||||
"Domain over which plotting occurs must have non-zero support.");
|
||||
|
||||
double branchAdjust = 0;
|
||||
|
||||
Point2D.Double prevPoint = new Point2D.Double();
|
||||
Point2D.Double point = new Point2D.Double();
|
||||
|
||||
/*
|
||||
* Setup the iteration of the point plotter. Start at the origin of the specified domain.
|
||||
*/
|
||||
double t = domain.getBegin();
|
||||
plotter.plot(t, point);
|
||||
|
||||
/*
|
||||
* Prepare to take the first "step", constrain to the end of the domain if it over steps.
|
||||
*/
|
||||
int i = 1;
|
||||
t = Math.min(domain.getBegin() + step, domain.getEnd());
|
||||
|
||||
/*
|
||||
* Check to see if the starting point is exactly one of the poles. If it is then peek ahead to
|
||||
* the next point. We will set the longitude of the point (which is meaningless in general,
|
||||
* except in this projection) to match that value.
|
||||
*/
|
||||
if ((point.y == 0.0) || (point.y == 1.0)) {
|
||||
plotter.plot(t, prevPoint);
|
||||
point.x = prevPoint.x;
|
||||
}
|
||||
|
||||
/*
|
||||
* Move to the start of the path, and set the previous point to the path starting location.
|
||||
*/
|
||||
buffer.moveTo(point.x, point.y);
|
||||
prevPoint.setLocation(point);
|
||||
|
||||
while (t <= domain.getEnd()) {
|
||||
|
||||
plotter.plot(t, point);
|
||||
|
||||
/*
|
||||
* Apply the current branch adjustment to point.x.
|
||||
*/
|
||||
point.x += branchAdjust;
|
||||
|
||||
/*
|
||||
* Check to see if point is the pole, if it is then adjust the longitude to match that of the
|
||||
* previous point. This check is made after, since prevPoint has already had it's branch
|
||||
* adjustment made in the previous iteration.
|
||||
*/
|
||||
if ((point.y == 0.0) || (point.y == 1.0)) {
|
||||
point.x = prevPoint.x;
|
||||
}
|
||||
|
||||
/*
|
||||
* Now check to see if the separation between this point and the next exceeds split.
|
||||
*/
|
||||
if (Math.abs(point.x - prevPoint.x) > split) {
|
||||
|
||||
/*
|
||||
* If point.x is closer to branchLeft, then adjust it to the right.
|
||||
*/
|
||||
if (point.x < midPoint) {
|
||||
branchAdjust += branchRight - branchLeft;
|
||||
point.x += branchRight - branchLeft;
|
||||
} else {
|
||||
branchAdjust -= branchRight - branchLeft;
|
||||
point.x -= branchRight - branchLeft;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
buffer.lineTo(point.x, point.y);
|
||||
|
||||
/*
|
||||
* Prepare to enter the next iteration. First check to see if we are on the final point of the
|
||||
* iteration. If we are, then simply return the path we've been creating. Otherwise continue
|
||||
* on.
|
||||
*/
|
||||
if (t == domain.getEnd()) {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
prevPoint.setLocation(point);
|
||||
|
||||
i++;
|
||||
|
||||
/*
|
||||
* Update t to the next step, constrain it to the end of the domain.
|
||||
*/
|
||||
t = Math.min(domain.getBegin() + i * step, domain.getEnd());
|
||||
}
|
||||
|
||||
/*
|
||||
* We should never reach here if the iteration through the steps works properly.
|
||||
*/
|
||||
throw new BugException("Unable to project shape. This should never happen and is "
|
||||
+ "indicative of a failure in the algorithm.");
|
||||
|
||||
}
|
||||
|
||||
static Point2D getPathStart(Path2D path) {
|
||||
Point2D result = path.getCurrentPoint();
|
||||
double[] segment = new double[6];
|
||||
path.getPathIterator(null).currentSegment(segment);
|
||||
result.setLocation(segment[0], segment[1]);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public double getNativeAspectRatio() {
|
||||
return 2.0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape getValidRegion() {
|
||||
return new Rectangle2D.Double(0, 0, 1, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point2D getReference() {
|
||||
return new Point2D.Double(0.5, 0.5);
|
||||
}
|
||||
|
||||
}
|
||||
350
src/main/java/picante/math/cones/SimpleCylindricalPathType.java
Normal file
350
src/main/java/picante/math/cones/SimpleCylindricalPathType.java
Normal file
@@ -0,0 +1,350 @@
|
||||
package picante.math.cones;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import java.awt.Shape;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Path2D;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
|
||||
/**
|
||||
* Package private enumeration that captures the various types of path closures that can happen when
|
||||
* dealing with simple cylindrical projections of the entire body (2*PI radian longitude range).
|
||||
*/
|
||||
enum SimpleCylindricalPathType {
|
||||
|
||||
/**
|
||||
* Indicates the path to close contains the north pole, but requires a shift to the left to close
|
||||
* properly.
|
||||
*/
|
||||
NORTH_POLE_LEFT_SHIFT(true) {
|
||||
|
||||
@Override
|
||||
void close(Path2D path, Point2D start, Point2D end, double branchRightShift,
|
||||
double overPoleBorderPadding) {
|
||||
shiftAndCloseOverPole(path, end.getY(), start.getY(), -branchRightShift,
|
||||
end.getX() + overPoleBorderPadding,
|
||||
start.getX() - branchRightShift - overPoleBorderPadding, -2 * overPoleBorderPadding);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Indicates the path to close contains the north pole and requires no shift to close.
|
||||
*/
|
||||
NORTH_POLE_NO_SHIFT(true) {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Override
|
||||
void close(Path2D path, Point2D start, Point2D end, double branchRightShift,
|
||||
double overPoleBorderPadding) {
|
||||
|
||||
/*
|
||||
* If the path moves left to right
|
||||
*/
|
||||
if (end.getX() > start.getX()) {
|
||||
closeOverPole(path, end.getY(), start.getY(), 1 + overPoleBorderPadding,
|
||||
-overPoleBorderPadding, -2 * overPoleBorderPadding);
|
||||
} else {
|
||||
closeOverPole(path, end.getY(), start.getY(), -overPoleBorderPadding,
|
||||
1 + overPoleBorderPadding, -2 * overPoleBorderPadding);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Indicates the path to close contains the north pole, but requires a shift to the right to close
|
||||
* properly.
|
||||
*/
|
||||
NORTH_POLE_RIGHT_SHIFT(true) {
|
||||
|
||||
@Override
|
||||
void close(Path2D path, Point2D start, Point2D end, double branchRightShift,
|
||||
double overPoleBorderPadding) {
|
||||
shiftAndCloseOverPole(path, end.getY(), start.getY(), branchRightShift,
|
||||
end.getX() - overPoleBorderPadding,
|
||||
start.getX() + branchRightShift + overPoleBorderPadding, -2 * overPoleBorderPadding);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Indicates the second path to join requires a shift to the left by the branch offset.
|
||||
*/
|
||||
LEFT_SHIFT(false) {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Override
|
||||
void close(Path2D path, Point2D start, Point2D end, double branchRightShift,
|
||||
double overPoleBorderPadding) {
|
||||
path.closePath();
|
||||
path.append(createShiftedPath(path, -branchRightShift, 0.0), false);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Indicates the second path to join requires no shift.
|
||||
*/
|
||||
NO_SHIFT(false) {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Override
|
||||
void close(Path2D path, Point2D start, Point2D end, double branchRightShift,
|
||||
double overPoleBorderPadding) {
|
||||
path.closePath();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Indicates the second path to join requires a shift to the right by the branch offset.
|
||||
*/
|
||||
RIGHT_SHIFT(false) {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Override
|
||||
void close(Path2D path, Point2D start, Point2D end, double branchRightShift,
|
||||
double overPoleBorderPadding) {
|
||||
path.closePath();
|
||||
path.append(createShiftedPath(path, branchRightShift, 0.0), false);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Indicates the path to close contains the south pole, but requires a shift to the left to close
|
||||
* properly.
|
||||
*/
|
||||
SOUTH_POLE_LEFT_SHIFT(true) {
|
||||
|
||||
@Override
|
||||
void close(Path2D path, Point2D start, Point2D end, double branchRightShift,
|
||||
double overPoleBorderPadding) {
|
||||
shiftAndCloseOverPole(path, end.getY(), start.getY(), -branchRightShift,
|
||||
end.getX() + overPoleBorderPadding,
|
||||
start.getX() - branchRightShift - overPoleBorderPadding, 1 + 2 * overPoleBorderPadding);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Indicates the path to close contains the south pole, but requires no shift to close properly.
|
||||
*/
|
||||
SOUTH_POLE_NO_SHIFT(true) {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Override
|
||||
void close(Path2D path, Point2D start, Point2D end, double branchRightShift,
|
||||
double overPoleBorderPadding) {
|
||||
/*
|
||||
* If the path moves from left to right
|
||||
*/
|
||||
if (end.getX() > start.getX()) {
|
||||
closeOverPole(path, end.getY(), start.getY(), 1 + overPoleBorderPadding,
|
||||
-overPoleBorderPadding, 1 + 2 * overPoleBorderPadding);
|
||||
} else {
|
||||
closeOverPole(path, end.getY(), start.getY(), -overPoleBorderPadding,
|
||||
1 + overPoleBorderPadding, 1 + 2 * overPoleBorderPadding);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Indicates the path to close contains the south pole, but requires a shift to the right to close
|
||||
* properly.
|
||||
*/
|
||||
SOUTH_POLE_RIGHT_SHIFT(true) {
|
||||
|
||||
@Override
|
||||
void close(Path2D path, Point2D start, Point2D end, double branchRightShift,
|
||||
double overPoleBorderPadding) {
|
||||
shiftAndCloseOverPole(path, end.getY(), start.getY(), branchRightShift,
|
||||
end.getX() - overPoleBorderPadding,
|
||||
start.getX() + branchRightShift + overPoleBorderPadding, 1 + 2 * overPoleBorderPadding);
|
||||
}
|
||||
};
|
||||
|
||||
private final boolean isPolar;
|
||||
|
||||
private SimpleCylindricalPathType(boolean isPolar) {
|
||||
this.isPolar = isPolar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the path join type contains a pole or not.
|
||||
*
|
||||
* @return true if the type contains either pole
|
||||
*/
|
||||
boolean isPolar() {
|
||||
return isPolar;
|
||||
}
|
||||
|
||||
abstract void close(Path2D path, Point2D start, Point2D end, double branchRightShift,
|
||||
double overPoleBorderPadding);
|
||||
|
||||
/**
|
||||
* Closes a path over the pole by drawing a padded rectangular box around the outside of the
|
||||
* projection.
|
||||
* <p>
|
||||
* This is a simple utility method to consolidate the execution of the same code with different
|
||||
* parameters in the pole shift combinations.
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* ============================================================================<=====+--outerLat
|
||||
* |
|
||||
* |
|
||||
* --------+------------------------------------------------+-------- Math.PI/2.0 |
|
||||
* | | ^
|
||||
* | | |
|
||||
* | | |
|
||||
* . . . .+ . . . . . . .o--------------------->-----------+--------------x===>=====+--firstLat
|
||||
* | | |
|
||||
* | | firstLon
|
||||
* | |
|
||||
* | |
|
||||
* --------+------------------------------------------------+-------- -Math.PI/2.0
|
||||
*
|
||||
* lowerBranchValue lowerBranchValue + (2 * Math.PI)
|
||||
* </pre>
|
||||
*
|
||||
* @param path the path to close over the pole.
|
||||
* @param firstLat the latitude of the first point in the enclosing box (generally path's end
|
||||
* point y-value)
|
||||
* @param lastLat the latitude of the last point in the enclosing box (generally path's start
|
||||
* point y-value)
|
||||
* @param branchShift the signed branch shift direction
|
||||
* @param firstLon the longitude of the first padded point (usually path's end point x-value with
|
||||
* a padding)
|
||||
* @param lastLon the longitude of the last padded point (usually path's start point x-value with
|
||||
* a padding applied along with a branch shift)
|
||||
* @param outerLat the padded value of latitude
|
||||
*/
|
||||
static void shiftAndCloseOverPole(Path2D path, double firstLat, double lastLat,
|
||||
double branchShift, double firstLon, double lastLon, double outerLat) {
|
||||
|
||||
/*
|
||||
* Append the shifted path to the start of the supplied path.
|
||||
*/
|
||||
Path2D shifted = createShiftedPath(path, branchShift, 0);
|
||||
shifted.append(path, true);
|
||||
path.reset();
|
||||
path.append(shifted, false);
|
||||
|
||||
/*
|
||||
* Draw the box outside of the projection.
|
||||
*/
|
||||
path.lineTo(firstLon, firstLat);
|
||||
path.lineTo(firstLon, outerLat);
|
||||
path.lineTo(lastLon, outerLat);
|
||||
path.lineTo(lastLon, lastLat);
|
||||
|
||||
path.closePath();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes a path over the pole without any shifting.
|
||||
*
|
||||
* @param path the open path
|
||||
* @param firstLat the latitude associated with the first closure point
|
||||
* @param lastLat the latitude associated with the last closure point
|
||||
* @param firstLon the longitude associated with the first closure point
|
||||
* @param lastLon the longitude associated with the last closure point
|
||||
* @param outerLat the latitude of the over the pole value to utilize
|
||||
*/
|
||||
static void closeOverPole(Path2D path, double firstLat, double lastLat, double firstLon,
|
||||
double lastLon, double outerLat) {
|
||||
path.lineTo(firstLon, firstLat);
|
||||
path.lineTo(firstLon, outerLat);
|
||||
path.lineTo(lastLon, outerLat);
|
||||
path.lineTo(lastLon, lastLat);
|
||||
path.closePath();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Identifies the type of a closed path.
|
||||
*
|
||||
* @param path a closed path that has been enumerated from start to end
|
||||
* @param overPoleBranchTestTolerance
|
||||
* @param branchLeft
|
||||
* @param branchRight
|
||||
*
|
||||
* @return the type of path for which closure is to be performed.
|
||||
*/
|
||||
static SimpleCylindricalPathType identifyType(Path2D path, Point2D start, Point2D end,
|
||||
double overPoleBranchTestTolerance, double branchLeft, double branchRight) {
|
||||
|
||||
/*
|
||||
* Determine if the region contains either pole. If it does, then it will have a single branch
|
||||
* path that does not "close" back onto itself; despite the fact it is actually closed.
|
||||
*/
|
||||
if (Math.abs(end.getX() - start.getX()) > overPoleBranchTestTolerance) {
|
||||
|
||||
/*
|
||||
* Examine the start of the path to determine which pole it is closer to. If it's exactly 0.5,
|
||||
* assume it's enclosing the south pole.
|
||||
*
|
||||
* TODO: Consider alternatives, as this isn't really robust.
|
||||
*/
|
||||
if (start.getY() < 0.5) {
|
||||
if (end.getX() < branchLeft) {
|
||||
return SimpleCylindricalPathType.NORTH_POLE_RIGHT_SHIFT;
|
||||
} else if (end.getX() > branchRight) {
|
||||
return SimpleCylindricalPathType.NORTH_POLE_LEFT_SHIFT;
|
||||
} else {
|
||||
return SimpleCylindricalPathType.NORTH_POLE_NO_SHIFT;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle the south pole cases.
|
||||
*/
|
||||
if (end.getX() < branchLeft) {
|
||||
return SimpleCylindricalPathType.SOUTH_POLE_RIGHT_SHIFT;
|
||||
} else if (end.getX() > branchRight) {
|
||||
return SimpleCylindricalPathType.SOUTH_POLE_LEFT_SHIFT;
|
||||
} else {
|
||||
return SimpleCylindricalPathType.SOUTH_POLE_NO_SHIFT;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* It's not a polar situation, examine the bounding rectangle to determine which type of shift
|
||||
* is necessary.
|
||||
*/
|
||||
Rectangle2D bounds = path.getBounds2D();
|
||||
|
||||
if (bounds.getMinX() < branchLeft) {
|
||||
return SimpleCylindricalPathType.RIGHT_SHIFT;
|
||||
}
|
||||
if (bounds.getMaxX() > branchRight) {
|
||||
return SimpleCylindricalPathType.LEFT_SHIFT;
|
||||
}
|
||||
return SimpleCylindricalPathType.NO_SHIFT;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a shifted version of the supplied path coordinates by the specified deltas in X and Y.
|
||||
* <p>
|
||||
* This is different than {@link AffineTransform#createTransformedShape(java.awt.Shape)} because
|
||||
* it returns a Path2D with the translated coordinates, instead of {@link Shape}.
|
||||
* </p>
|
||||
*/
|
||||
static Path2D createShiftedPath(Path2D path, double deltaX, double deltaY) {
|
||||
|
||||
checkNotNull(path);
|
||||
|
||||
AffineTransform transform = AffineTransform.getTranslateInstance(deltaX, deltaY);
|
||||
|
||||
/*
|
||||
* Use Path2D.Double, unless the supplied path is Path2D.Float.
|
||||
*/
|
||||
if (path instanceof Path2D.Float) {
|
||||
return new Path2D.Float(path, transform);
|
||||
}
|
||||
return new Path2D.Double(path, transform);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package picante.math.coords;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import picante.math.vectorspace.MatrixIJK;
|
||||
import picante.math.vectorspace.UnwritableVectorIJK;
|
||||
|
||||
/**
|
||||
* This is a helper class intended to encapsulate the two state methods on the
|
||||
* {@link CoordConverter} interface. Since these methods are really just leveraging a
|
||||
* {@link Transformation}, they need not be reimplimented every time.
|
||||
*
|
||||
* Particularly, this class is meant to ensure the Thread safety of implementations of
|
||||
* {@link CoordConverter} in a consistent way. It is important that the incoming
|
||||
* {@link Transformation} is thread safe(preferably stateless).
|
||||
*
|
||||
* TODO The other two methods should also be accomplished leveraging some other class, this will
|
||||
* then ensure the thread safety of those two methods as well. Also, this returns the templated
|
||||
* classes {@link WritableState} and {@link State}, not the concrete classes, so these methods will
|
||||
* need to be casted if you want the real state.
|
||||
*
|
||||
* @author G.K.Stephens
|
||||
*
|
||||
* @param <C> a {@link AbstractVector} type
|
||||
*/
|
||||
abstract class AbstractCoordConverter<C extends AbstractVector> implements CoordConverter<C> {
|
||||
|
||||
private final Transformation<C> jacobian;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param jacobian the incoming Jacobian must be thread safe for this class to be thread safe.
|
||||
* Implementations contained in this package are assumed to be thread safe.
|
||||
*/
|
||||
public AbstractCoordConverter(Transformation<C> jacobian) {
|
||||
this.jacobian = checkNotNull(jacobian);
|
||||
}
|
||||
|
||||
// we know that C extends UC
|
||||
@Override
|
||||
public State<C> toCoordinate(State<UnwritableVectorIJK> cartesian) {
|
||||
|
||||
MatrixIJK matrix = new MatrixIJK();
|
||||
|
||||
C position = toCoordinate(cartesian.getPosition());
|
||||
|
||||
jacobian.getInverseTransformation(position, matrix);
|
||||
|
||||
C velocity = jacobian.mxv(matrix, cartesian.getVelocity());
|
||||
|
||||
return construct(position, velocity);
|
||||
}
|
||||
|
||||
abstract State<C> construct(C position, C velocity);
|
||||
|
||||
@Override
|
||||
public State<UnwritableVectorIJK> toCartesian(State<C> coordinate) {
|
||||
|
||||
MatrixIJK matrix = new MatrixIJK();
|
||||
|
||||
UnwritableVectorIJK position = toCartesian(coordinate.getPosition());
|
||||
|
||||
jacobian.getTransformation(coordinate.getPosition(), matrix);
|
||||
|
||||
UnwritableVectorIJK velocity = jacobian.mxv(matrix, coordinate.getVelocity());
|
||||
|
||||
return new CartesianState(position, velocity);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package picante.math.coords;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import picante.math.vectorspace.MatrixIJ;
|
||||
import picante.math.vectorspace.UnwritableVectorIJ;
|
||||
|
||||
/**
|
||||
* This is a helper class intended to encapsulate the two state methods on the
|
||||
* {@link CoordConverter} interface. Since these methods are really just leveraging a
|
||||
* {@link Transformation}, they need not be reimplimented every time.
|
||||
*
|
||||
* Particularly, this class is meant to ensure the Thread safety of implementations of
|
||||
* {@link CoordConverter} in a consistent way. It is important that the incoming
|
||||
* {@link Transformation} is thread safe(preferably stateless).
|
||||
*
|
||||
* TODO The other two methods should also be accomplished leveraging some other class, this will
|
||||
* then ensure the thread safety of those two methods as well. Also, this returns the templated
|
||||
* classes {@link WritableState} and {@link State}, not the concrete classes, so these methods will
|
||||
* need to be casted if you want the real state.
|
||||
*
|
||||
* @author G.K.Stephens
|
||||
*
|
||||
* @param <C> a {@link AbstractVectorIJ} type
|
||||
*/
|
||||
abstract class AbstractCoordConverterIJ<C extends AbstractVectorIJ> implements CoordConverterIJ<C> {
|
||||
|
||||
private final TransformationIJ<C> jacobian;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param jacobian the incoming Jacobian must be thread safe for this class to be thread safe.
|
||||
* Implementations contained in this package are assumed to be thread safe.
|
||||
*/
|
||||
public AbstractCoordConverterIJ(TransformationIJ<C> jacobian) {
|
||||
this.jacobian = checkNotNull(jacobian);
|
||||
}
|
||||
|
||||
// we know that C extends UC
|
||||
@Override
|
||||
public State<C> toCoordinate(State<UnwritableVectorIJ> cartesian) {
|
||||
|
||||
MatrixIJ matrix = new MatrixIJ();
|
||||
|
||||
C position = toCoordinate(cartesian.getPosition());
|
||||
|
||||
jacobian.getInverseTransformation(position, matrix);
|
||||
|
||||
C velocity = jacobian.mxv(matrix, cartesian.getVelocity());
|
||||
|
||||
return construct(position, velocity);
|
||||
}
|
||||
|
||||
abstract State<C> construct(C position, C velocity);
|
||||
|
||||
@Override
|
||||
public State<UnwritableVectorIJ> toCartesian(State<C> coordinate) {
|
||||
|
||||
MatrixIJ matrix = new MatrixIJ();
|
||||
|
||||
UnwritableVectorIJ position = toCartesian(coordinate.getPosition());
|
||||
|
||||
jacobian.getTransformation(coordinate.getPosition(), matrix);
|
||||
|
||||
UnwritableVectorIJ velocity = jacobian.mxv(matrix, coordinate.getVelocity());
|
||||
|
||||
return new CartesianStateIJ(position, velocity);
|
||||
}
|
||||
|
||||
}
|
||||
108
src/main/java/picante/math/coords/AbstractState.java
Normal file
108
src/main/java/picante/math/coords/AbstractState.java
Normal file
@@ -0,0 +1,108 @@
|
||||
package picante.math.coords;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import picante.designpatterns.Writable;
|
||||
|
||||
/**
|
||||
* An abstract class that will help implementors of {@link State}. This class was made to help
|
||||
* minimize the amount of Boiler plate code implementors of afore mentioned interfaces would have to
|
||||
* write. In keeping with this spirit, it should also make the different implementations have a
|
||||
* similar outline. If the implementor is attempting to follow the Weak-Immutability pattern (
|
||||
* {@link Writable}), then you can only use this for the unwritable version, as the writable version
|
||||
* can't extend both the unwritable and this.
|
||||
*
|
||||
* Everything that is protected in this class is meant to be exposed only for the Writable versions.
|
||||
*
|
||||
* @author G.K.Stephens
|
||||
*
|
||||
* @param <C> The unwritable version of a coordinate class.
|
||||
* @param <W> The writable version of a coordinate class.
|
||||
*/
|
||||
abstract class AbstractState<C> implements State<C> {
|
||||
/**
|
||||
* The field containing the buffer that holds the position component of the state.
|
||||
*/
|
||||
private final C position;
|
||||
|
||||
/**
|
||||
* The field containing the buffer that holds the velocity component of the state.
|
||||
*/
|
||||
private final C velocity;
|
||||
|
||||
/**
|
||||
* Creates a state.
|
||||
*
|
||||
* @param position the position of one object relative to another.
|
||||
*
|
||||
* @param velocity the time derivative of the supplied position.
|
||||
*/
|
||||
public AbstractState(C position, C velocity) {
|
||||
this.position = checkNotNull(position);
|
||||
this.velocity = checkNotNull(velocity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public C getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public C getVelocity() {
|
||||
return velocity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[position=" + position + ", velocity=" + velocity + "]";
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public final int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((position == null) ? 0 : position.hashCode());
|
||||
result = prime * result + ((velocity == null) ? 0 : velocity.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public final boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (!this.getClass().isInstance(obj)) {
|
||||
return false;
|
||||
}
|
||||
AbstractState other = (AbstractState) obj;
|
||||
if (position == null) {
|
||||
if (other.position != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!position.equals(other.position)) {
|
||||
return false;
|
||||
}
|
||||
if (velocity == null) {
|
||||
if (other.velocity != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!velocity.equals(other.velocity)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
111
src/main/java/picante/math/coords/AbstractVector.java
Normal file
111
src/main/java/picante/math/coords/AbstractVector.java
Normal file
@@ -0,0 +1,111 @@
|
||||
package picante.math.coords;
|
||||
|
||||
import picante.math.vectorspace.UnwritableVectorIJK;
|
||||
import picante.math.vectorspace.VectorIJK;
|
||||
|
||||
/**
|
||||
* This abstract class is meant to assist implementors of new coordinate types. The unwritable
|
||||
* coordinate should extend this guy. It is also meant to help ensure that the outlines for all
|
||||
* coordinates are consistent. This was also done, since the Jacobian classes require interaction
|
||||
* with the Coordinates as VectorIJKs, specifically for calling mxv on a coordinate. This makes that
|
||||
* possible. There may be further times where a coordinate needs to be used as a {@link VectorIJK},
|
||||
* as long as this occurs in this package, it is possible.
|
||||
*
|
||||
* The fact that a {@link VectorIJK} is the true composition class of all coordinate systems should
|
||||
* not leave this package. We don't want different coordinate systems to be VectorIJKs as you wont
|
||||
* know what coordinate system it is supposed to be.
|
||||
*
|
||||
* Also, if any other methods are needed on every coordinate class, they can hopefully just be added
|
||||
* in here.
|
||||
*
|
||||
* @author G.K.Stephens
|
||||
*/
|
||||
abstract class AbstractVector {
|
||||
|
||||
// The knowledge that this guy exists should never escape the package.
|
||||
private final UnwritableVectorIJK ijkCoordinate;
|
||||
|
||||
/**
|
||||
* Constructs a coordinate from the three basic components.
|
||||
*/
|
||||
public AbstractVector(double i, double j, double k) {
|
||||
this.ijkCoordinate = new UnwritableVectorIJK(i, j, k);
|
||||
}
|
||||
|
||||
// These six methods should be wrapped in new methods with the appropriate
|
||||
// names. The getters in the unwritable and the setters in the writable.
|
||||
// These six methods should never have there visibility upgraded, but should
|
||||
// be wrapped.
|
||||
/**
|
||||
* THIS METHOD SHOULD NOT BE PUBLIC
|
||||
*/
|
||||
double getI() {
|
||||
return this.ijkCoordinate.getI();
|
||||
}
|
||||
|
||||
/**
|
||||
* THIS METHOD SHOULD NOT BE PUBLIC
|
||||
*/
|
||||
double getJ() {
|
||||
return this.ijkCoordinate.getJ();
|
||||
}
|
||||
|
||||
/**
|
||||
* THIS METHOD SHOULD NOT BE PUBLIC
|
||||
*/
|
||||
double getK() {
|
||||
return this.ijkCoordinate.getK();
|
||||
}
|
||||
|
||||
/**
|
||||
* THIS METHOD SHOULD NOT BE PUBLIC
|
||||
*/
|
||||
// This method should never have its visibility upgraded.
|
||||
UnwritableVectorIJK getVectorIJK() {
|
||||
return this.ijkCoordinate;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public final int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((ijkCoordinate == null) ? 0 : ijkCoordinate.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public final boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (!this.getClass().isInstance(obj)) {
|
||||
return false;
|
||||
}
|
||||
AbstractVector other = (AbstractVector) obj;
|
||||
if (ijkCoordinate == null) {
|
||||
if (other.ijkCoordinate != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!ijkCoordinate.equals(other.ijkCoordinate)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
abstract public String toString();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package picante.math.coords;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
class AbstractVectorFieldValue<C> implements VectorFieldValue<C> {
|
||||
/**
|
||||
* The field containing the buffer that holds the position component of the state.
|
||||
*/
|
||||
private final C position;
|
||||
|
||||
/**
|
||||
* The field containing the buffer that holds the value component of the state.
|
||||
*/
|
||||
private final C value;
|
||||
|
||||
/**
|
||||
* Creates a state.
|
||||
*
|
||||
* @param position the position of one object relative to another.
|
||||
*
|
||||
* @param value the time derivative of the supplied position.
|
||||
*/
|
||||
public AbstractVectorFieldValue(C position, C value) {
|
||||
this.position = checkNotNull(position);
|
||||
this.value = checkNotNull(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public C getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public C getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[position=" + position + ", value=" + value + "]";
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public final int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((position == null) ? 0 : position.hashCode());
|
||||
result = prime * result + ((value == null) ? 0 : value.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public final boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (!this.getClass().isInstance(obj)) {
|
||||
return false;
|
||||
}
|
||||
AbstractVectorFieldValue other = (AbstractVectorFieldValue) obj;
|
||||
if (position == null) {
|
||||
if (other.position != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!position.equals(other.position)) {
|
||||
return false;
|
||||
}
|
||||
if (value == null) {
|
||||
if (other.value != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!value.equals(other.value)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
104
src/main/java/picante/math/coords/AbstractVectorIJ.java
Normal file
104
src/main/java/picante/math/coords/AbstractVectorIJ.java
Normal file
@@ -0,0 +1,104 @@
|
||||
package picante.math.coords;
|
||||
|
||||
import picante.math.vectorspace.UnwritableVectorIJ;
|
||||
import picante.math.vectorspace.VectorIJ;
|
||||
|
||||
/**
|
||||
* This abstract class is meant to assist implementors of new coordinate types. The unwritable
|
||||
* coordinate should extend this guy. It is also meant to help ensure that the outlines for all
|
||||
* coordinates are consistent. This was also done, since the Jacobian classes require interaction
|
||||
* with the Coordinates as VectorIJs, specifically for calling mxv on a coordinate. This makes that
|
||||
* possible. There may be further times where a coordinate needs to be used as a {@link VectorIJ},
|
||||
* as long as this occurs in this package, it is possible.
|
||||
*
|
||||
* The fact that a {@link VectorIJ} is the true composition class of all coordinate systems should
|
||||
* not leave this package. We don't want different coordinate systems to be VectorIJs as you wont
|
||||
* know what coordinate system it is supposed to be.
|
||||
*
|
||||
* Also, if any other methods are needed on every coordinate class, they can hopefully just be added
|
||||
* in here.
|
||||
*
|
||||
* @author G.K.Stephens
|
||||
*/
|
||||
abstract class AbstractVectorIJ {
|
||||
|
||||
// The knowledge that this guy exists should never escape the package.
|
||||
private final UnwritableVectorIJ ijCoordinate;
|
||||
|
||||
/**
|
||||
* Constructs a coordinate from the three basic components.
|
||||
*/
|
||||
public AbstractVectorIJ(double i, double j) {
|
||||
this.ijCoordinate = new UnwritableVectorIJ(i, j);
|
||||
}
|
||||
|
||||
// These six methods should be wrapped in new methods with the appropriate
|
||||
// names. The getters in the unwritable and the setters in the writable.
|
||||
// These six methods should never have there visibility upgraded, but should
|
||||
// be wrapped.
|
||||
/**
|
||||
* THIS METHOD SHOULD NOT BE PUBLIC
|
||||
*/
|
||||
double getI() {
|
||||
return this.ijCoordinate.getI();
|
||||
}
|
||||
|
||||
/**
|
||||
* THIS METHOD SHOULD NOT BE PUBLIC
|
||||
*/
|
||||
double getJ() {
|
||||
return this.ijCoordinate.getJ();
|
||||
}
|
||||
|
||||
/**
|
||||
* THIS METHOD SHOULD NOT BE PUBLIC
|
||||
*/
|
||||
// This method should never have its visibility upgraded.
|
||||
UnwritableVectorIJ getVectorIJ() {
|
||||
return this.ijCoordinate;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public final int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((ijCoordinate == null) ? 0 : ijCoordinate.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public final boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (!this.getClass().isInstance(obj)) {
|
||||
return false;
|
||||
}
|
||||
AbstractVectorIJ other = (AbstractVectorIJ) obj;
|
||||
if (ijCoordinate == null) {
|
||||
if (other.ijCoordinate != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!ijCoordinate.equals(other.ijCoordinate)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
abstract public String toString();
|
||||
|
||||
}
|
||||
593
src/main/java/picante/math/coords/AssertTools.java
Normal file
593
src/main/java/picante/math/coords/AssertTools.java
Normal file
@@ -0,0 +1,593 @@
|
||||
package picante.math.coords;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import picante.math.intervals.UnwritableInterval;
|
||||
import picante.math.vectorspace.RotationMatrixIJK;
|
||||
import picante.math.vectorspace.UnwritableMatrixIJ;
|
||||
import picante.math.vectorspace.UnwritableMatrixIJK;
|
||||
import picante.math.vectorspace.UnwritableRotationMatrixIJK;
|
||||
import picante.math.vectorspace.UnwritableVectorIJ;
|
||||
import picante.math.vectorspace.UnwritableVectorIJK;
|
||||
import picante.mechanics.UnwritableStateTransform;
|
||||
import picante.mechanics.UnwritableStateVector;
|
||||
import picante.mechanics.rotations.AxisAndAngle;
|
||||
|
||||
public class AssertTools {
|
||||
|
||||
/**
|
||||
* Assert two doubles are exactly equal.
|
||||
*
|
||||
* @param expected Expected double value.
|
||||
* @param actual Actual double value.
|
||||
*/
|
||||
public static void assertEqualDouble(double expected, double actual) {
|
||||
assertEquals(expected, actual, 0.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert two doubles are equivalent.
|
||||
*
|
||||
* @param expected Expected double value.
|
||||
* @param actual Actual double value.
|
||||
*/
|
||||
public static void assertEquivalentDouble(double expected, double actual) {
|
||||
assertEquals(expected, actual, Math.ulp(expected));
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert two doubles are within the appropriate relative tolerance.
|
||||
*
|
||||
* @param expected Expected double value.
|
||||
* @param actual Actual double value.
|
||||
* @param delta Required relative tolerance.
|
||||
*/
|
||||
public static void assertRelativeEquality(double expected, double actual, double delta) {
|
||||
if (expected != actual) {
|
||||
assertEquals(expected, actual, delta * Math.max(Math.abs(expected), Math.abs(actual)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert two vectors are equivalent.
|
||||
*
|
||||
* @param expected Expected vector.
|
||||
* @param actual Actual vector.
|
||||
*/
|
||||
public static void assertEquivalentVector(SphericalVector expected, SphericalVector actual) {
|
||||
assertEquivalentDouble(expected.getRadius(), actual.getRadius());
|
||||
assertEquivalentDouble(expected.getColatitude(), actual.getColatitude());
|
||||
assertEquivalentDouble(expected.getLongitude(), actual.getLongitude());
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert two vectors are exactly equal.
|
||||
*
|
||||
* @param expected Expected vector.
|
||||
* @param actual Actual vector.
|
||||
*/
|
||||
public static void assertEqualVector(SphericalVector expected, SphericalVector actual) {
|
||||
assertEqualDouble(expected.getRadius(), actual.getRadius());
|
||||
assertEqualDouble(expected.getColatitude(), actual.getColatitude());
|
||||
assertEqualDouble(expected.getLongitude(), actual.getLongitude());
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert component-wise equality with tolerance.
|
||||
*
|
||||
* @param expected Expected vector.
|
||||
* @param actual Actual vector.
|
||||
* @param delta Component-wise delta factor.
|
||||
*/
|
||||
public static void assertComponentEquals(SphericalVector expected, SphericalVector actual,
|
||||
double delta) {
|
||||
assertEquals(expected.getRadius(), actual.getRadius(), delta);
|
||||
assertEquals(expected.getColatitude(), actual.getColatitude(), delta);
|
||||
assertEquals(expected.getLongitude(), actual.getLongitude(), delta);
|
||||
}
|
||||
|
||||
public static void assertComponentRelativeEquality(SphericalVector expected,
|
||||
SphericalVector actual, double delta) {
|
||||
assertRelativeEquality(expected.getRadius(), actual.getRadius(), delta);
|
||||
assertRelativeEquality(expected.getColatitude(), actual.getColatitude(), delta);
|
||||
assertRelativeEquality(expected.getLongitude(), actual.getLongitude(), delta);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert two vectors are equivalent.
|
||||
*
|
||||
* @param expected Expected vector.
|
||||
* @param actual Actual vector.
|
||||
*/
|
||||
public static void assertEquivalentVector(LatitudinalVector expected, LatitudinalVector actual) {
|
||||
assertEquivalentDouble(expected.getRadius(), actual.getRadius());
|
||||
assertEquivalentDouble(expected.getLatitude(), actual.getLatitude());
|
||||
assertEquivalentDouble(expected.getLongitude(), actual.getLongitude());
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert two vectors are exactly equal.
|
||||
*
|
||||
* @param expected Expected vector.
|
||||
* @param actual Actual vector.
|
||||
*/
|
||||
public static void assertEqualVector(LatitudinalVector expected, LatitudinalVector actual) {
|
||||
assertEqualDouble(expected.getRadius(), actual.getRadius());
|
||||
assertEqualDouble(expected.getLatitude(), actual.getLatitude());
|
||||
assertEqualDouble(expected.getLongitude(), actual.getLongitude());
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert component-wise equality with tolerance.
|
||||
*
|
||||
* @param expected Expected vector.
|
||||
* @param actual Actual vector.
|
||||
* @param delta Component-wise delta factor.
|
||||
*/
|
||||
public static void assertComponentEquals(LatitudinalVector expected, LatitudinalVector actual,
|
||||
double delta) {
|
||||
assertEquals(expected.getRadius(), actual.getRadius(), delta);
|
||||
assertEquals(expected.getLatitude(), actual.getLatitude(), delta);
|
||||
assertEquals(expected.getLongitude(), actual.getLongitude(), delta);
|
||||
}
|
||||
|
||||
public static void assertComponentRelativeEquality(LatitudinalVector expected,
|
||||
LatitudinalVector actual, double delta) {
|
||||
assertRelativeEquality(expected.getRadius(), actual.getRadius(), delta);
|
||||
assertRelativeEquality(expected.getLatitude(), actual.getLatitude(), delta);
|
||||
assertRelativeEquality(expected.getLongitude(), actual.getLongitude(), delta);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert two vectors are equivalent.
|
||||
*
|
||||
* @param expected Expected vector.
|
||||
* @param actual Actual vector.
|
||||
*/
|
||||
public static void assertEquivalentVector(RaDecVector expected, RaDecVector actual) {
|
||||
assertEquivalentDouble(expected.getRadius(), actual.getRadius());
|
||||
assertEquivalentDouble(expected.getRightAscension(), actual.getRightAscension());
|
||||
assertEquivalentDouble(expected.getDeclination(), actual.getDeclination());
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert two vectors are exactly equal.
|
||||
*
|
||||
* @param expected Expected vector.
|
||||
* @param actual Actual vector.
|
||||
*/
|
||||
public static void assertEqualVector(RaDecVector expected, RaDecVector actual) {
|
||||
assertEqualDouble(expected.getRadius(), actual.getRadius());
|
||||
assertEqualDouble(expected.getRightAscension(), actual.getRightAscension());
|
||||
assertEqualDouble(expected.getDeclination(), actual.getDeclination());
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert component-wise equality with tolerance.
|
||||
*
|
||||
* @param expected Expected vector.
|
||||
* @param actual Actual vector.
|
||||
* @param delta Component-wise delta factor.
|
||||
*/
|
||||
public static void assertComponentEquals(RaDecVector expected, RaDecVector actual, double delta) {
|
||||
assertEquals(expected.getRadius(), actual.getRadius(), delta);
|
||||
assertEquals(expected.getRightAscension(), actual.getRightAscension(), delta);
|
||||
assertEquals(expected.getDeclination(), actual.getDeclination(), delta);
|
||||
}
|
||||
|
||||
public static void assertComponentRelativeEquality(RaDecVector expected, RaDecVector actual,
|
||||
double delta) {
|
||||
assertRelativeEquality(expected.getRadius(), actual.getRadius(), delta);
|
||||
assertRelativeEquality(expected.getRightAscension(), actual.getRightAscension(), delta);
|
||||
assertRelativeEquality(expected.getDeclination(), actual.getDeclination(), delta);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Assert two vectors are equivalent.
|
||||
*
|
||||
* @param expected Expected vector.
|
||||
* @param actual Actual vector.
|
||||
*/
|
||||
public static void assertEquivalentVector(CylindricalVector expected, CylindricalVector actual) {
|
||||
assertEquivalentDouble(expected.getCylindricalRadius(), actual.getCylindricalRadius());
|
||||
assertEquivalentDouble(expected.getLongitude(), actual.getLongitude());
|
||||
assertEquivalentDouble(expected.getHeight(), actual.getHeight());
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert two vectors are exactly equal.
|
||||
*
|
||||
* @param expected Expected vector.
|
||||
* @param actual Actual vector.
|
||||
*/
|
||||
public static void assertEqualVector(CylindricalVector expected, CylindricalVector actual) {
|
||||
assertEqualDouble(expected.getCylindricalRadius(), actual.getCylindricalRadius());
|
||||
assertEqualDouble(expected.getLongitude(), actual.getLongitude());
|
||||
assertEqualDouble(expected.getHeight(), actual.getHeight());
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert component-wise equality with tolerance.
|
||||
*
|
||||
* @param expected Expected vector.
|
||||
* @param actual Actual vector.
|
||||
* @param delta Component-wise delta factor.
|
||||
*/
|
||||
public static void assertComponentEquals(CylindricalVector expected, CylindricalVector actual,
|
||||
double delta) {
|
||||
assertEquals(expected.getCylindricalRadius(), actual.getCylindricalRadius(), delta);
|
||||
assertEquals(expected.getLongitude(), actual.getLongitude(), delta);
|
||||
assertEquals(expected.getHeight(), actual.getHeight(), delta);
|
||||
}
|
||||
|
||||
public static void assertComponentRelativeEquality(CylindricalVector expected,
|
||||
CylindricalVector actual, double delta) {
|
||||
assertRelativeEquality(expected.getCylindricalRadius(), actual.getCylindricalRadius(), delta);
|
||||
assertRelativeEquality(expected.getLongitude(), actual.getLongitude(), delta);
|
||||
assertRelativeEquality(expected.getHeight(), actual.getHeight(), delta);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert component-wise equality with tolerance.
|
||||
*
|
||||
* @param expected Expected vector.
|
||||
* @param actual Actual vector.
|
||||
* @param delta Component-wise delta factor.
|
||||
*/
|
||||
public static void assertComponentEquals(PolarVector expected, PolarVector actual, double delta) {
|
||||
assertEquals(expected.getRadius(), actual.getRadius(), delta);
|
||||
assertEquals(expected.getAngle(), actual.getAngle(), delta);
|
||||
}
|
||||
|
||||
public static void assertComponentRelativeEquality(PolarVector expected, PolarVector actual,
|
||||
double delta) {
|
||||
assertRelativeEquality(expected.getRadius(), actual.getRadius(), delta);
|
||||
assertRelativeEquality(expected.getAngle(), actual.getAngle(), delta);
|
||||
}
|
||||
|
||||
|
||||
public static void assertComponentEquals(SphericalState expected, SphericalState actual,
|
||||
double delta) {
|
||||
assertComponentEquals(expected.getPosition(), actual.getPosition(), delta);
|
||||
assertComponentEquals(expected.getVelocity(), actual.getVelocity(), delta);
|
||||
}
|
||||
|
||||
public static void assertComponentRelativeEquality(SphericalState expected, SphericalState actual,
|
||||
double delta) {
|
||||
assertComponentRelativeEquality(expected.getPosition(), actual.getPosition(), delta);
|
||||
assertComponentRelativeEquality(expected.getVelocity(), actual.getVelocity(), delta);
|
||||
}
|
||||
|
||||
public static void assertComponentEquals(LatitudinalState expected, LatitudinalState actual,
|
||||
double delta) {
|
||||
assertComponentEquals(expected.getPosition(), actual.getPosition(), delta);
|
||||
assertComponentEquals(expected.getVelocity(), actual.getVelocity(), delta);
|
||||
}
|
||||
|
||||
public static void assertComponentRelativeEquality(LatitudinalState expected,
|
||||
LatitudinalState actual, double delta) {
|
||||
assertComponentRelativeEquality(expected.getPosition(), actual.getPosition(), delta);
|
||||
assertComponentRelativeEquality(expected.getVelocity(), actual.getVelocity(), delta);
|
||||
}
|
||||
|
||||
public static void assertComponentEquals(CylindricalState expected, CylindricalState actual,
|
||||
double delta) {
|
||||
assertComponentEquals(expected.getPosition(), actual.getPosition(), delta);
|
||||
assertComponentEquals(expected.getVelocity(), actual.getVelocity(), delta);
|
||||
}
|
||||
|
||||
public static void assertComponentRelativeEquality(CylindricalState expected,
|
||||
CylindricalState actual, double delta) {
|
||||
assertComponentRelativeEquality(expected.getPosition(), actual.getPosition(), delta);
|
||||
assertComponentRelativeEquality(expected.getVelocity(), actual.getVelocity(), delta);
|
||||
}
|
||||
|
||||
public static void assertComponentEquals(CartesianState expected, CartesianState actual,
|
||||
double delta) {
|
||||
assertComponentEquals(expected.getPosition(), actual.getPosition(), delta);
|
||||
assertComponentEquals(expected.getVelocity(), actual.getVelocity(), delta);
|
||||
}
|
||||
|
||||
public static void assertComponentRelativeEquality(CartesianState expected, CartesianState actual,
|
||||
double delta) {
|
||||
assertComponentRelativeEquality(expected.getPosition(), actual.getPosition(), delta);
|
||||
assertComponentRelativeEquality(expected.getVelocity(), actual.getVelocity(), delta);
|
||||
}
|
||||
|
||||
public static void assertComponentEquals(CartesianStateIJ expected, CartesianStateIJ actual,
|
||||
double delta) {
|
||||
assertComponentEquals(expected.getPosition(), actual.getPosition(), delta);
|
||||
assertComponentEquals(expected.getVelocity(), actual.getVelocity(), delta);
|
||||
}
|
||||
|
||||
public static void assertComponentEquals(PolarState expected, PolarState actual, double delta) {
|
||||
assertComponentEquals(expected.getPosition(), actual.getPosition(), delta);
|
||||
assertComponentEquals(expected.getVelocity(), actual.getVelocity(), delta);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert two matrices are equivalent.
|
||||
*
|
||||
* @param expected Expected matrix.
|
||||
* @param actual Actual matrix.
|
||||
*/
|
||||
public static void assertEquivalentMatrix(UnwritableMatrixIJK expected,
|
||||
UnwritableMatrixIJK actual) {
|
||||
assertEquivalentDouble(expected.getII(), actual.getII());
|
||||
assertEquivalentDouble(expected.getIJ(), actual.getIJ());
|
||||
assertEquivalentDouble(expected.getIK(), actual.getIK());
|
||||
assertEquivalentDouble(expected.getJI(), actual.getJI());
|
||||
assertEquivalentDouble(expected.getJJ(), actual.getJJ());
|
||||
assertEquivalentDouble(expected.getJK(), actual.getJK());
|
||||
assertEquivalentDouble(expected.getKI(), actual.getKI());
|
||||
assertEquivalentDouble(expected.getKJ(), actual.getKJ());
|
||||
assertEquivalentDouble(expected.getKK(), actual.getKK());
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert two matrices are exactly equal.
|
||||
*
|
||||
* @param expected Expected matrix.
|
||||
* @param actual Actual matrix.
|
||||
*/
|
||||
public static void assertEqualMatrix(UnwritableMatrixIJK expected, UnwritableMatrixIJK actual) {
|
||||
assertEqualDouble(expected.getII(), actual.getII());
|
||||
assertEqualDouble(expected.getIJ(), actual.getIJ());
|
||||
assertEqualDouble(expected.getIK(), actual.getIK());
|
||||
assertEqualDouble(expected.getJI(), actual.getJI());
|
||||
assertEqualDouble(expected.getJJ(), actual.getJJ());
|
||||
assertEqualDouble(expected.getJK(), actual.getJK());
|
||||
assertEqualDouble(expected.getKI(), actual.getKI());
|
||||
assertEqualDouble(expected.getKJ(), actual.getKJ());
|
||||
assertEqualDouble(expected.getKK(), actual.getKK());
|
||||
}
|
||||
|
||||
public static void assertComponentEquals(String message, UnwritableMatrixIJK expected,
|
||||
UnwritableMatrixIJK actual, double delta) {
|
||||
assertEquals(message, expected.getII(), actual.getII(), delta);
|
||||
assertEquals(message, expected.getJI(), actual.getJI(), delta);
|
||||
assertEquals(message, expected.getKI(), actual.getKI(), delta);
|
||||
assertEquals(message, expected.getIJ(), actual.getIJ(), delta);
|
||||
assertEquals(message, expected.getJJ(), actual.getJJ(), delta);
|
||||
assertEquals(message, expected.getKJ(), actual.getKJ(), delta);
|
||||
assertEquals(message, expected.getIK(), actual.getIK(), delta);
|
||||
assertEquals(message, expected.getJK(), actual.getJK(), delta);
|
||||
assertEquals(message, expected.getKK(), actual.getKK(), delta);
|
||||
}
|
||||
|
||||
public static void assertComponentEquals(UnwritableMatrixIJK expected, UnwritableMatrixIJK actual,
|
||||
double delta) {
|
||||
assertEquals(expected.getII(), actual.getII(), delta);
|
||||
assertEquals(expected.getJI(), actual.getJI(), delta);
|
||||
assertEquals(expected.getKI(), actual.getKI(), delta);
|
||||
assertEquals(expected.getIJ(), actual.getIJ(), delta);
|
||||
assertEquals(expected.getJJ(), actual.getJJ(), delta);
|
||||
assertEquals(expected.getKJ(), actual.getKJ(), delta);
|
||||
assertEquals(expected.getIK(), actual.getIK(), delta);
|
||||
assertEquals(expected.getJK(), actual.getJK(), delta);
|
||||
assertEquals(expected.getKK(), actual.getKK(), delta);
|
||||
}
|
||||
|
||||
public static void assertComponentRelativeEquality(UnwritableMatrixIJK expected,
|
||||
UnwritableMatrixIJK actual, double delta) {
|
||||
assertRelativeEquality(expected.getII(), actual.getII(), delta);
|
||||
assertRelativeEquality(expected.getJI(), actual.getJI(), delta);
|
||||
assertRelativeEquality(expected.getKI(), actual.getKI(), delta);
|
||||
assertRelativeEquality(expected.getIJ(), actual.getIJ(), delta);
|
||||
assertRelativeEquality(expected.getJJ(), actual.getJJ(), delta);
|
||||
assertRelativeEquality(expected.getKJ(), actual.getKJ(), delta);
|
||||
assertRelativeEquality(expected.getIK(), actual.getIK(), delta);
|
||||
assertRelativeEquality(expected.getJK(), actual.getJK(), delta);
|
||||
assertRelativeEquality(expected.getKK(), actual.getKK(), delta);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert two vectors are equivalent.
|
||||
*
|
||||
* @param expected Expected vector.
|
||||
* @param actual Actual vector.
|
||||
*/
|
||||
public static void assertEquivalentVector(UnwritableVectorIJK expected,
|
||||
UnwritableVectorIJK actual) {
|
||||
assertEquivalentDouble(expected.getI(), actual.getI());
|
||||
assertEquivalentDouble(expected.getJ(), actual.getJ());
|
||||
assertEquivalentDouble(expected.getK(), actual.getK());
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert two vectors are exactly equal.
|
||||
*
|
||||
* @param expected Expected vector.
|
||||
* @param actual Actual vector.
|
||||
*/
|
||||
public static void assertEqualVector(UnwritableVectorIJK expected, UnwritableVectorIJK actual) {
|
||||
assertEqualDouble(expected.getI(), actual.getI());
|
||||
assertEqualDouble(expected.getJ(), actual.getJ());
|
||||
assertEqualDouble(expected.getK(), actual.getK());
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert component-wise equality with tolerance.
|
||||
*
|
||||
* @param expected Expected vector.
|
||||
* @param actual Actual vector.
|
||||
* @param delta Component-wise delta factor.
|
||||
*/
|
||||
public static void assertComponentEquals(UnwritableVectorIJK expected, UnwritableVectorIJK actual,
|
||||
double delta) {
|
||||
assertEquals(expected.getI(), actual.getI(), delta);
|
||||
assertEquals(expected.getJ(), actual.getJ(), delta);
|
||||
assertEquals(expected.getK(), actual.getK(), delta);
|
||||
}
|
||||
|
||||
public static void assertComponentRelativeEquality(UnwritableVectorIJK expected,
|
||||
UnwritableVectorIJK actual, double delta) {
|
||||
assertRelativeEquality(expected.getI(), actual.getI(), delta);
|
||||
assertRelativeEquality(expected.getJ(), actual.getJ(), delta);
|
||||
assertRelativeEquality(expected.getK(), actual.getK(), delta);
|
||||
}
|
||||
|
||||
public static void assertRotationAngleEquals(UnwritableRotationMatrixIJK expected,
|
||||
UnwritableRotationMatrixIJK actual, double toleranceInRadians) {
|
||||
assertEquals(0.0,
|
||||
Math.abs(new AxisAndAngle(RotationMatrixIJK.mtxm(actual, expected)).getAngle()),
|
||||
toleranceInRadians);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert two matrices are equivalent.
|
||||
*
|
||||
* @param expected Expected matrix.
|
||||
* @param actual Actual matrix.
|
||||
*/
|
||||
public static void assertEquivalentMatrix(UnwritableMatrixIJ expected,
|
||||
UnwritableMatrixIJ actual) {
|
||||
assertEquivalentDouble(expected.getII(), actual.getII());
|
||||
assertEquivalentDouble(expected.getIJ(), actual.getIJ());
|
||||
assertEquivalentDouble(expected.getJI(), actual.getJI());
|
||||
assertEquivalentDouble(expected.getJJ(), actual.getJJ());
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert two matrices are exactly equal.
|
||||
*
|
||||
* @param expected Expected matrix.
|
||||
* @param actual Actual matrix.
|
||||
*/
|
||||
public static void assertEqualMatrix(UnwritableMatrixIJ expected, UnwritableMatrixIJ actual) {
|
||||
assertEqualDouble(expected.getII(), actual.getII());
|
||||
assertEqualDouble(expected.getIJ(), actual.getIJ());
|
||||
assertEqualDouble(expected.getJI(), actual.getJI());
|
||||
assertEqualDouble(expected.getJJ(), actual.getJJ());
|
||||
}
|
||||
|
||||
public static void assertComponentEquals(String message, UnwritableMatrixIJ expected,
|
||||
UnwritableMatrixIJ actual, double delta) {
|
||||
assertEquals(message, expected.getII(), actual.getII(), delta);
|
||||
assertEquals(message, expected.getJI(), actual.getJI(), delta);
|
||||
assertEquals(message, expected.getIJ(), actual.getIJ(), delta);
|
||||
assertEquals(message, expected.getJJ(), actual.getJJ(), delta);
|
||||
}
|
||||
|
||||
public static void assertComponentEquals(UnwritableMatrixIJ expected, UnwritableMatrixIJ actual,
|
||||
double delta) {
|
||||
assertEquals(expected.getII(), actual.getII(), delta);
|
||||
assertEquals(expected.getJI(), actual.getJI(), delta);
|
||||
assertEquals(expected.getIJ(), actual.getIJ(), delta);
|
||||
assertEquals(expected.getJJ(), actual.getJJ(), delta);
|
||||
}
|
||||
|
||||
public static void assertComponentRelativeEquality(UnwritableMatrixIJ expected,
|
||||
UnwritableMatrixIJ actual, double delta) {
|
||||
assertRelativeEquality(expected.getII(), actual.getII(), delta);
|
||||
assertRelativeEquality(expected.getJI(), actual.getJI(), delta);
|
||||
assertRelativeEquality(expected.getIJ(), actual.getIJ(), delta);
|
||||
assertRelativeEquality(expected.getJJ(), actual.getJJ(), delta);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert two vectors are equivalent.
|
||||
*
|
||||
* @param expected Expected vector.
|
||||
* @param actual Actual vector.
|
||||
*/
|
||||
public static void assertEquivalentVector(UnwritableVectorIJ expected,
|
||||
UnwritableVectorIJ actual) {
|
||||
assertEquivalentDouble(expected.getI(), actual.getI());
|
||||
assertEquivalentDouble(expected.getJ(), actual.getJ());
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert two vectors are exactly equal.
|
||||
*
|
||||
* @param expected Expected vector.
|
||||
* @param actual Actual vector.
|
||||
*/
|
||||
public static void assertEqualVector(UnwritableVectorIJ expected, UnwritableVectorIJ actual) {
|
||||
assertEqualDouble(expected.getI(), actual.getI());
|
||||
assertEqualDouble(expected.getJ(), actual.getJ());
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert component-wise equality with tolerance.
|
||||
*
|
||||
* @param expected Expected vector.
|
||||
* @param actual Actual vector.
|
||||
* @param delta Component-wise delta factor.
|
||||
*/
|
||||
public static void assertComponentEquals(UnwritableVectorIJ expected, UnwritableVectorIJ actual,
|
||||
double delta) {
|
||||
assertEquals(expected.getI(), actual.getI(), delta);
|
||||
assertEquals(expected.getJ(), actual.getJ(), delta);
|
||||
}
|
||||
|
||||
public static void assertComponentRelativeEquality(UnwritableVectorIJ expected,
|
||||
UnwritableVectorIJ actual, double delta) {
|
||||
assertRelativeEquality(expected.getI(), actual.getI(), delta);
|
||||
assertRelativeEquality(expected.getJ(), actual.getJ(), delta);
|
||||
}
|
||||
|
||||
public static void assertEquivalentStateTransform(UnwritableStateTransform expected,
|
||||
UnwritableStateTransform actual) {
|
||||
assertEquivalentMatrix(expected.getRotation(), actual.getRotation());
|
||||
assertEquivalentMatrix(expected.getRotationDerivative(), actual.getRotationDerivative());
|
||||
}
|
||||
|
||||
public static void assertEqualStateTransform(UnwritableStateTransform expected,
|
||||
UnwritableStateTransform actual) {
|
||||
assertEqualMatrix(expected.getRotation(), actual.getRotation());
|
||||
assertEqualMatrix(expected.getRotationDerivative(), actual.getRotationDerivative());
|
||||
}
|
||||
|
||||
public static void assertComponentEquals(UnwritableStateTransform expected,
|
||||
UnwritableStateTransform actual, double delta) {
|
||||
assertComponentEquals(expected.getRotation(), actual.getRotation(), delta);
|
||||
assertComponentEquals(expected.getRotationDerivative(), actual.getRotationDerivative(), delta);
|
||||
}
|
||||
|
||||
public static void assertComponentEquals(String message, UnwritableStateTransform expected,
|
||||
UnwritableStateTransform actual, double delta) {
|
||||
assertComponentEquals(message, expected.getRotation(), actual.getRotation(), delta);
|
||||
assertComponentEquals(message, expected.getRotationDerivative(), actual.getRotationDerivative(),
|
||||
delta);
|
||||
}
|
||||
|
||||
public static void assertComponentRelativeEquality(UnwritableStateTransform expected,
|
||||
UnwritableStateTransform actual, double delta) {
|
||||
assertComponentRelativeEquality(expected.getRotation(), actual.getRotation(), delta);
|
||||
assertComponentRelativeEquality(expected.getRotationDerivative(),
|
||||
actual.getRotationDerivative(), delta);
|
||||
}
|
||||
|
||||
public static void assertEquivalentStateVector(UnwritableStateVector expected,
|
||||
UnwritableStateVector actual) {
|
||||
assertEquivalentVector(expected.getPosition(), actual.getPosition());
|
||||
assertEquivalentVector(expected.getVelocity(), actual.getVelocity());
|
||||
}
|
||||
|
||||
public static void assertEqualStateVector(UnwritableStateVector expected,
|
||||
UnwritableStateVector actual) {
|
||||
assertEqualVector(expected.getPosition(), actual.getPosition());
|
||||
assertEqualVector(expected.getVelocity(), actual.getVelocity());
|
||||
}
|
||||
|
||||
public static void assertComponentEquals(UnwritableStateVector expected,
|
||||
UnwritableStateVector actual, double delta) {
|
||||
assertComponentEquals(expected.getPosition(), actual.getPosition(), delta);
|
||||
assertComponentEquals(expected.getVelocity(), actual.getVelocity(), delta);
|
||||
}
|
||||
|
||||
public static void assertComponentRelativeEquality(UnwritableStateVector expected,
|
||||
UnwritableStateVector actual, double delta) {
|
||||
assertComponentRelativeEquality(expected.getPosition(), actual.getPosition(), delta);
|
||||
assertComponentRelativeEquality(expected.getVelocity(), actual.getVelocity(), delta);
|
||||
}
|
||||
|
||||
public static void assertEqualInterval(UnwritableInterval expected, UnwritableInterval actual,
|
||||
double tolerance) {
|
||||
assertEquals(expected.getBegin(), actual.getBegin(), tolerance);
|
||||
assertEquals(expected.getEnd(), actual.getEnd(), tolerance);
|
||||
}
|
||||
|
||||
}
|
||||
39
src/main/java/picante/math/coords/CartesianState.java
Normal file
39
src/main/java/picante/math/coords/CartesianState.java
Normal file
@@ -0,0 +1,39 @@
|
||||
package picante.math.coords;
|
||||
|
||||
import picante.math.vectorspace.UnwritableVectorIJK;
|
||||
import picante.math.vectorspace.VectorIJK;
|
||||
import picante.mechanics.UnwritableStateVector;
|
||||
|
||||
/**
|
||||
* A State containing two {@link VectorIJK}, one for the Cartesian position and one for the
|
||||
* Cartesian Velocity. Similar to the {@link UnwritableStateVector} classes.
|
||||
*
|
||||
* @author G.K.Stephens
|
||||
*
|
||||
*/
|
||||
public final class CartesianState extends AbstractState<UnwritableVectorIJK> {
|
||||
|
||||
/**
|
||||
* The zero state vector: zero position and zero velocity components.
|
||||
*/
|
||||
public static final CartesianState ZERO = new CartesianState(VectorIJK.ZERO, VectorIJK.ZERO);
|
||||
|
||||
/**
|
||||
* Creates a state vector. Defensive copies are made of the input vectors.
|
||||
*
|
||||
* @param position the position of one object relative to another.
|
||||
*
|
||||
* @param velocity the time derivative of the supplied position.
|
||||
*/
|
||||
public CartesianState(UnwritableVectorIJK position, UnwritableVectorIJK velocity) {
|
||||
super(UnwritableVectorIJK.copyOf(position), UnwritableVectorIJK.copyOf(velocity));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String toString() {
|
||||
return "[" + super.getPosition().getI() + "," + super.getPosition().getJ() + ","
|
||||
+ super.getPosition().getK() + "; " + super.getVelocity().getI() + ","
|
||||
+ super.getVelocity().getJ() + "," + super.getVelocity().getK() + "]";
|
||||
}
|
||||
|
||||
}
|
||||
38
src/main/java/picante/math/coords/CartesianStateIJ.java
Normal file
38
src/main/java/picante/math/coords/CartesianStateIJ.java
Normal file
@@ -0,0 +1,38 @@
|
||||
package picante.math.coords;
|
||||
|
||||
import picante.math.vectorspace.UnwritableVectorIJ;
|
||||
import picante.math.vectorspace.VectorIJ;
|
||||
import picante.mechanics.UnwritableStateVector;
|
||||
|
||||
/**
|
||||
* A State containing two {@link VectorIJ}, one for the Cartesian position and one for the Cartesian
|
||||
* Velocity. Similar to the {@link UnwritableStateVector} classes.
|
||||
*
|
||||
* @author G.K.Stephens
|
||||
*
|
||||
*/
|
||||
public final class CartesianStateIJ extends AbstractState<UnwritableVectorIJ> {
|
||||
|
||||
/**
|
||||
* The zero state vector: zero position and zero velocity components.
|
||||
*/
|
||||
public static final CartesianStateIJ ZERO = new CartesianStateIJ(VectorIJ.ZERO, VectorIJ.ZERO);
|
||||
|
||||
/**
|
||||
* Creates a state vector. Defensive copies are made of the input vectors.
|
||||
*
|
||||
* @param position the position of one object relative to another.
|
||||
*
|
||||
* @param velocity the time derivative of the supplied position.
|
||||
*/
|
||||
public CartesianStateIJ(UnwritableVectorIJ position, UnwritableVectorIJ velocity) {
|
||||
super(UnwritableVectorIJ.copyOf(position), UnwritableVectorIJ.copyOf(velocity));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String toString() {
|
||||
return "[" + super.getPosition().getI() + "," + super.getPosition().getJ() + "; "
|
||||
+ super.getVelocity().getI() + "," + super.getVelocity().getJ() + "]";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package picante.math.coords;
|
||||
|
||||
import picante.math.vectorspace.UnwritableVectorIJK;
|
||||
|
||||
/**
|
||||
* A container class for a Cartesian coordinate <img src="./doc-files/cartCoord.png"/> and vector
|
||||
* field value <img src="./doc-files/cartVect.png"/> at that coordinate.
|
||||
*
|
||||
* @author G.K.Stephens
|
||||
*
|
||||
*/
|
||||
public final class CartesianVectorFieldValue extends AbstractVectorFieldValue<UnwritableVectorIJK> {
|
||||
|
||||
/**
|
||||
* @param position a Cartesian coordinate <img src="./doc-files/cartCoord.png"/>
|
||||
* @param value a Cartesian vector field value <img src="./doc-files/cartVect.png"/> at that
|
||||
* coordinate
|
||||
*/
|
||||
public CartesianVectorFieldValue(UnwritableVectorIJK position, UnwritableVectorIJK value) {
|
||||
super(UnwritableVectorIJK.copyOf(position), UnwritableVectorIJK.copyOf(value));
|
||||
}
|
||||
|
||||
}
|
||||
53
src/main/java/picante/math/coords/CoordConverter.java
Normal file
53
src/main/java/picante/math/coords/CoordConverter.java
Normal file
@@ -0,0 +1,53 @@
|
||||
package picante.math.coords;
|
||||
|
||||
import picante.math.vectorspace.UnwritableVectorIJK;
|
||||
|
||||
/**
|
||||
* An interface which allows for the conversion between different coordinate systems to Cartesian.
|
||||
* The four methods allow you to convert positions and states from Cartesian to the coordinate
|
||||
* system and back. It is templated on the Unwritable and Writable version of the coordinate class.
|
||||
*
|
||||
* @author G.K.Stephens
|
||||
*
|
||||
* @param <U> The Unwritable Coordinate class.
|
||||
* @param <W> The Writable version of the Coordinate class.
|
||||
*/
|
||||
interface CoordConverter<U> {
|
||||
|
||||
/**
|
||||
* Converts a Cartesian position to another coordinate system position.
|
||||
*
|
||||
* @param cartesian A {@link UnwritableVectorIJK} holding the Cartesian position.
|
||||
* @param coordinateBuffer A coordinate buffer holding the position in this coordinate system.
|
||||
* @return a reference to buffer for convenience.
|
||||
*/
|
||||
U toCoordinate(UnwritableVectorIJK cartesian);
|
||||
|
||||
/**
|
||||
* Converts a coordinate system position to a Cartesian position.
|
||||
*
|
||||
* @param coordinate A coordinate holding the position in this coordinate system.
|
||||
* @param cartesianBuffer A {@link UnwritableVectorIJK} buffer holding the Cartesian position.
|
||||
* @return a reference to buffer for convenience.
|
||||
*/
|
||||
UnwritableVectorIJK toCartesian(U coordinate);
|
||||
|
||||
/**
|
||||
* Converts a Cartesian state to another coordinate system state.
|
||||
*
|
||||
* @param cartesian A {@link UnwritableVectorIJK} holding the Cartesian state.
|
||||
* @param coordinateBuffer A coordinate buffer holding the state in this coordinate system.
|
||||
* @return a reference to buffer for convenience.
|
||||
*/
|
||||
State<U> toCoordinate(State<UnwritableVectorIJK> cartesian);
|
||||
|
||||
/**
|
||||
* Converts a coordinate system state to a Cartesian state.
|
||||
*
|
||||
* @param coordinate A coordinate state in this coordinate system.
|
||||
* @param cartesianBuffer A {@link WritableState} buffer holding the Cartesian state.
|
||||
* @return a reference to buffer for convenience.
|
||||
*/
|
||||
State<UnwritableVectorIJK> toCartesian(State<U> coordinate);
|
||||
|
||||
}
|
||||
53
src/main/java/picante/math/coords/CoordConverterIJ.java
Normal file
53
src/main/java/picante/math/coords/CoordConverterIJ.java
Normal file
@@ -0,0 +1,53 @@
|
||||
package picante.math.coords;
|
||||
|
||||
import picante.math.vectorspace.UnwritableVectorIJ;
|
||||
|
||||
/**
|
||||
* An interface which allows for the conversion between different coordinate systems to Cartesian.
|
||||
* The four methods allow you to convert positions and states from Cartesian to the coordinate
|
||||
* system and back. It is templated on the Unwritable and Writable version of the coordinate class.
|
||||
*
|
||||
* @author G.K.Stephens
|
||||
*
|
||||
* @param <U> The Unwritable Coordinate class.
|
||||
* @param <W> The Writable version of the Coordinate class.
|
||||
*/
|
||||
interface CoordConverterIJ<U> {
|
||||
|
||||
/**
|
||||
* Converts a Cartesian position to another coordinate system position.
|
||||
*
|
||||
* @param cartesian A {@link UnwritableVectorIJ} holding the Cartesian position.
|
||||
* @param coordinateBuffer A coordinate buffer holding the position in this coordinate system.
|
||||
* @return a reference to buffer for convenience.
|
||||
*/
|
||||
U toCoordinate(UnwritableVectorIJ cartesian);
|
||||
|
||||
/**
|
||||
* Converts a coordinate system position to a Cartesian position.
|
||||
*
|
||||
* @param coordinate A coordinate holding the position in this coordinate system.
|
||||
* @param cartesianBuffer A {@link UnwritableVectorIJ} buffer holding the Cartesian position.
|
||||
* @return a reference to buffer for convenience.
|
||||
*/
|
||||
UnwritableVectorIJ toCartesian(U coordinate);
|
||||
|
||||
/**
|
||||
* Converts a Cartesian state to another coordinate system state.
|
||||
*
|
||||
* @param cartesian A {@link UnwritableVectorIJ} holding the Cartesian state.
|
||||
* @param coordinateBuffer A coordinate buffer holding the state in this coordinate system.
|
||||
* @return a reference to buffer for convenience.
|
||||
*/
|
||||
State<U> toCoordinate(State<UnwritableVectorIJ> cartesian);
|
||||
|
||||
/**
|
||||
* Converts a coordinate system state to a Cartesian state.
|
||||
*
|
||||
* @param coordinate A coordinate state in this coordinate system.
|
||||
* @param cartesianBuffer A {@link WritableState} buffer holding the Cartesian state.
|
||||
* @return a reference to buffer for convenience.
|
||||
*/
|
||||
State<UnwritableVectorIJ> toCartesian(State<U> coordinate);
|
||||
|
||||
}
|
||||
253
src/main/java/picante/math/coords/CoordConverters.java
Normal file
253
src/main/java/picante/math/coords/CoordConverters.java
Normal file
@@ -0,0 +1,253 @@
|
||||
package picante.math.coords;
|
||||
|
||||
import picante.math.vectorspace.UnwritableVectorIJ;
|
||||
import picante.math.vectorspace.UnwritableVectorIJK;
|
||||
import picante.math.vectorspace.VectorIJ;
|
||||
import picante.math.vectorspace.VectorIJK;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author G.K.Stephens
|
||||
*
|
||||
*/
|
||||
public class CoordConverters {
|
||||
|
||||
private static final SphericalCoordConverter sphericalCoordConverter =
|
||||
new SphericalCoordConverter();
|
||||
private static final LatitudinalCoordConverter latitudinalCoordConverter =
|
||||
new LatitudinalCoordConverter();
|
||||
private static final RaDecCoordConverter raDecCoordConverter = new RaDecCoordConverter();
|
||||
private static final CylindricalCoordConverter cylindricalCoordConverter =
|
||||
new CylindricalCoordConverter();
|
||||
|
||||
private CoordConverters() {}
|
||||
|
||||
/**
|
||||
* Converts a Cartesian position to spherical position.
|
||||
*
|
||||
* @param cartesian A {@link UnwritableVectorIJK} holding the Cartesian position.
|
||||
* @param sphericalBuffer A {@link SphericalVector} buffer holding the spherical position;
|
||||
* @return a reference to buffer for convenience.
|
||||
*/
|
||||
public static SphericalVector convertToSpherical(UnwritableVectorIJK cartesian) {
|
||||
return sphericalCoordConverter.toCoordinate(cartesian);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a spherical position to a Cartesian position.
|
||||
*
|
||||
* @param spherical A {@link SphericalVector} holding the spherical position.
|
||||
* @param cartesianBuffer A {@link VectorIJK} buffer holding the Cartesian position.
|
||||
* @return a reference to buffer for convenience.
|
||||
*/
|
||||
public static UnwritableVectorIJK convert(SphericalVector spherical) {
|
||||
return sphericalCoordConverter.toCartesian(spherical);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a Cartesian state to a spherical state.
|
||||
*
|
||||
* @param cartesian A {@link CartesianState} holding the Cartesian state.
|
||||
* @param sphericalBuffer A {@link SphericalState} holding the state in this coordinate system.
|
||||
* @return a reference to buffer for convenience.
|
||||
*/
|
||||
public static SphericalState convertToSpherical(CartesianState cartesian) {
|
||||
return (SphericalState) sphericalCoordConverter.toCoordinate(cartesian);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a spherical state to a Cartesian state.
|
||||
*
|
||||
* @param spherical A {@link SphericalState} holding the Spherical state.
|
||||
* @param cartesianBuffer A {@link WritableState} buffer holding the Cartesian state.
|
||||
* @return a reference to buffer for convenience.
|
||||
*/
|
||||
public static CartesianState convert(SphericalState spherical) {
|
||||
return (CartesianState) sphericalCoordConverter.toCartesian(spherical);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts from Cartesian coordinates to Latitudinal coordinates
|
||||
*
|
||||
* @param cartesian
|
||||
* @param LatitudinalBuffer
|
||||
* @return
|
||||
*/
|
||||
public static LatitudinalVector convertToLatitudinal(UnwritableVectorIJK cartesian) {
|
||||
return latitudinalCoordConverter.toCoordinate(cartesian);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts from Latitudinal coordinates to Cartesian coordinates
|
||||
*
|
||||
* @param Latitudinal
|
||||
* @param cartesianBuffer
|
||||
* @return
|
||||
*/
|
||||
public static UnwritableVectorIJK convert(LatitudinalVector Latitudinal) {
|
||||
return latitudinalCoordConverter.toCartesian(Latitudinal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts from Cartesian states to Latitudinal states
|
||||
*
|
||||
* @param cartesian
|
||||
* @param LatitudinalBuffer
|
||||
* @return
|
||||
*/
|
||||
public static LatitudinalState convertToLatitudinal(CartesianState cartesian) {
|
||||
return (LatitudinalState) latitudinalCoordConverter.toCoordinate(cartesian);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts from Latitudinal states to Cartesian states
|
||||
*
|
||||
* @param Latitudinal
|
||||
* @param cartesianBuffer
|
||||
* @return
|
||||
*/
|
||||
public static CartesianState convert(LatitudinalState Latitudinal) {
|
||||
return (CartesianState) latitudinalCoordConverter.toCartesian(Latitudinal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts from Cartesian coordinates to RaDec coordinates
|
||||
*
|
||||
* @param cartesian
|
||||
* @param RaDecBuffer
|
||||
* @return
|
||||
*/
|
||||
public static RaDecVector convertToRaDec(UnwritableVectorIJK cartesian) {
|
||||
return raDecCoordConverter.toCoordinate(cartesian);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts from RaDec coordinates to Cartesian coordinates
|
||||
*
|
||||
* @param RaDec
|
||||
* @param cartesianBuffer
|
||||
* @return
|
||||
*/
|
||||
public static UnwritableVectorIJK convert(RaDecVector RaDec) {
|
||||
return raDecCoordConverter.toCartesian(RaDec);
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// /**
|
||||
// * Converts from Cartesian states to RaDec states
|
||||
// *
|
||||
// * @param cartesian
|
||||
// * @param RaDecBuffer
|
||||
// * @return
|
||||
// */
|
||||
// public static RaDecState convert(UnwritableCartesianState cartesian,
|
||||
// RaDecState raDecBuffer) {
|
||||
// return raDecCoordConverter.toCoordinate(cartesian, raDecBuffer);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Converts from RaDec states to Cartesian states
|
||||
// *
|
||||
// * @param RaDec
|
||||
// * @param cartesianBuffer
|
||||
// * @return
|
||||
// */
|
||||
// public static CartesianState convert(UnwritableRaDecState RaDec,
|
||||
// CartesianState cartesianBuffer) {
|
||||
// return raDecCoordConverter.toCartesian(RaDec, cartesianBuffer);
|
||||
// }
|
||||
|
||||
/**
|
||||
* Converts a Cartesian position to cylindrical position.
|
||||
*
|
||||
* @param cartesian A {@link UnwritableVectorIJK} holding the Cartesian position.
|
||||
* @param cylindricalBuffer A {@link CylindricalVector} buffer holding the cylindrical position;
|
||||
* @return a reference to buffer for convenience.
|
||||
*/
|
||||
public static CylindricalVector convertToCylindrical(UnwritableVectorIJK cartesian) {
|
||||
return cylindricalCoordConverter.toCoordinate(cartesian);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a cylindrical position to a Cartesian position.
|
||||
*
|
||||
* @param cylindrical A {@link CylindricalVector} holding the cylindrical position.
|
||||
* @param cartesianBuffer A {@link VectorIJK} buffer holding the Cartesian position.
|
||||
* @return a reference to buffer for convenience.
|
||||
*/
|
||||
public static UnwritableVectorIJK convert(CylindricalVector cylindrical) {
|
||||
return cylindricalCoordConverter.toCartesian(cylindrical);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a Cartesian state to a cylindrical state.
|
||||
*
|
||||
* @param cartesian A {@link CartesianState} holding the Cartesian state.
|
||||
* @param cylindricalBuffer A {@link CylindricalState} holding the state in this coordinate
|
||||
* system.
|
||||
* @return a reference to buffer for convenience.
|
||||
*/
|
||||
public static CylindricalState convertToCylindrical(CartesianState cartesian) {
|
||||
return (CylindricalState) cylindricalCoordConverter.toCoordinate(cartesian);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a cylindrical state to a Cartesian state.
|
||||
*
|
||||
* @param cylindrical A {@link CylindricalState} holding the Cylindrical state.
|
||||
* @param cartesianBuffer A {@link WritableState} buffer holding the Cartesian state.
|
||||
* @return a reference to buffer for convenience.
|
||||
*/
|
||||
public static CartesianState convert(CylindricalState cylindrical) {
|
||||
return (CartesianState) cylindricalCoordConverter.toCartesian(cylindrical);
|
||||
}
|
||||
|
||||
// the two dimensional guys
|
||||
private static PolarCoordConverter polarCoordConverter = new PolarCoordConverter();
|
||||
|
||||
/**
|
||||
* Converts a Cartesian position to polar position.
|
||||
*
|
||||
* @param cartesian A {@link UnwritableVectorIJ} holding the Cartesian position.
|
||||
* @param polarBuffer A {@link PolarVector} buffer holding the polar position;
|
||||
* @return a reference to buffer for convenience.
|
||||
*/
|
||||
public static PolarVector convertToPolar(UnwritableVectorIJ cartesian) {
|
||||
return polarCoordConverter.toCoordinate(cartesian);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a polar position to a Cartesian position.
|
||||
*
|
||||
* @param polar A {@link PolarVector} holding the polar position.
|
||||
* @param cartesianBuffer A {@link VectorIJ} buffer holding the Cartesian position.
|
||||
* @return a reference to buffer for convenience.
|
||||
*/
|
||||
public static UnwritableVectorIJ convert(PolarVector polar) {
|
||||
return polarCoordConverter.toCartesian(polar);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a Cartesian state to a polar state.
|
||||
*
|
||||
* @param cartesian A {@link UnwritableCartesianIJState} holding the Cartesian state.
|
||||
* @param polarBuffer A {@link PolarState} holding the state in this coordinate system.
|
||||
* @return a reference to buffer for convenience.
|
||||
*/
|
||||
public static PolarState convertToPolar(CartesianStateIJ cartesian) {
|
||||
return (PolarState) polarCoordConverter.toCoordinate(cartesian);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a polar state to a Cartesian state.
|
||||
*
|
||||
* @param polar A {@link PolarState} holding the Polar state.
|
||||
* @param cartesianBuffer A {@link WritableState} buffer holding the Cartesian state.
|
||||
* @return a reference to buffer for convenience.
|
||||
*/
|
||||
public static CartesianStateIJ convert(PolarState polar) {
|
||||
return (CartesianStateIJ) polarCoordConverter.toCartesian(polar);
|
||||
}
|
||||
|
||||
}
|
||||
31
src/main/java/picante/math/coords/CoordUtilities.java
Normal file
31
src/main/java/picante/math/coords/CoordUtilities.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package picante.math.coords;
|
||||
|
||||
import static picante.math.PicanteMath.PI;
|
||||
|
||||
/**
|
||||
* This class is for convenience methods related to angles and coordinate converters. Its not meant
|
||||
* to grow into an alternative coordinate conversion API, so if you find that you want to add lots
|
||||
* of methods here, see first if your conversion needs are already met with other existing
|
||||
* converters.
|
||||
*
|
||||
* @author vandejd1
|
||||
*/
|
||||
public class CoordUtilities {
|
||||
|
||||
private CoordUtilities() {};
|
||||
|
||||
/**
|
||||
* converts colatitude in radians to latitude in radians
|
||||
*/
|
||||
public static double toLatitude(double colatInRadians) {
|
||||
return PI / 2.0 - colatInRadians;
|
||||
}
|
||||
|
||||
/**
|
||||
* converts latitude in radians to colatitude in radians
|
||||
*/
|
||||
public static double toColatitude(double latInRadians) {
|
||||
return PI / 2.0 - latInRadians;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package picante.math.coords;
|
||||
|
||||
import static picante.math.PicanteMath.abs;
|
||||
import static picante.math.PicanteMath.atan2;
|
||||
import static picante.math.PicanteMath.cos;
|
||||
import static picante.math.PicanteMath.max;
|
||||
import static picante.math.PicanteMath.sin;
|
||||
import static picante.math.PicanteMath.sqrt;
|
||||
import picante.math.vectorspace.UnwritableVectorIJK;
|
||||
import picante.units.FundamentalPhysicalConstants;
|
||||
|
||||
class CylindricalCoordConverter extends AbstractCoordConverter<CylindricalVector> {
|
||||
|
||||
private final static CylindricalToCartesianJacobian JACOBIAN =
|
||||
new CylindricalToCartesianJacobian();
|
||||
|
||||
public CylindricalCoordConverter() {
|
||||
super(JACOBIAN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CylindricalVector toCoordinate(UnwritableVectorIJK cartesian) {
|
||||
/*
|
||||
* Use temporary variables for computing R. C BIG = MAX( DABS(RECTAN(1)), DABS(RECTAN(2)) )
|
||||
*
|
||||
* Convert to cylindrical coordinates C Z = RECTAN(3 )
|
||||
*
|
||||
* IF ( BIG .EQ. 0 ) THEN R = 0.D0 LONG = 0.D0 ELSE X = RECTAN(1) / BIG Y = RECTAN(2) / BIG
|
||||
*
|
||||
* R = BIG * DSQRT (X*X + Y*Y)
|
||||
*
|
||||
* LONG = DATAN2 (Y,X) END IF
|
||||
*
|
||||
* IF ( LONG .LT. 0.D0) THEN LONG = LONG + TWOPI() END IF
|
||||
*/
|
||||
|
||||
// C
|
||||
// C Use temporary variables for computing R.
|
||||
// C
|
||||
double big = max(abs(cartesian.getI()), abs(cartesian.getJ()));
|
||||
|
||||
// C
|
||||
// C Convert to cylindrical coordinates
|
||||
// C
|
||||
double height = cartesian.getK();
|
||||
|
||||
double cylindricalRadius = 0;
|
||||
double longitude = 0;
|
||||
|
||||
if (big == 0.0) {
|
||||
cylindricalRadius = 0.0;
|
||||
longitude = 0.0;
|
||||
} else {
|
||||
double x = cartesian.getI() / big;
|
||||
double y = cartesian.getJ() / big;
|
||||
|
||||
cylindricalRadius = big * sqrt(x * x + y * y);
|
||||
|
||||
longitude = atan2(y, x);
|
||||
}
|
||||
|
||||
if (longitude < 0.0) {
|
||||
longitude = longitude + FundamentalPhysicalConstants.TWOPI;
|
||||
}
|
||||
|
||||
return new CylindricalVector(cylindricalRadius, longitude, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnwritableVectorIJK toCartesian(CylindricalVector coordinate) {
|
||||
|
||||
/*
|
||||
* From the SPICE routine cylrec.f.
|
||||
*
|
||||
* Convert to rectangular coordinates, storing the results in C temporary variables. C X = R *
|
||||
* DCOS(LONG) Y = R * DSIN(LONG)
|
||||
*
|
||||
* Move the results to the output variables. C RECTAN(1) = X RECTAN(2) = Y RECTAN(3) = Z
|
||||
*/
|
||||
|
||||
return new UnwritableVectorIJK(
|
||||
coordinate.getCylindricalRadius() * cos(coordinate.getLongitude()),
|
||||
coordinate.getCylindricalRadius() * sin(coordinate.getLongitude()), coordinate.getHeight());
|
||||
}
|
||||
|
||||
@Override
|
||||
State<CylindricalVector> construct(CylindricalVector position, CylindricalVector velocity) {
|
||||
return new CylindricalState(position, velocity);
|
||||
}
|
||||
|
||||
}
|
||||
29
src/main/java/picante/math/coords/CylindricalState.java
Normal file
29
src/main/java/picante/math/coords/CylindricalState.java
Normal file
@@ -0,0 +1,29 @@
|
||||
package picante.math.coords;
|
||||
|
||||
|
||||
public final class CylindricalState extends AbstractState<CylindricalVector> {
|
||||
|
||||
/**
|
||||
* The zero state vector: zero position and zero velocity components.
|
||||
*/
|
||||
public static final CylindricalState ZERO =
|
||||
new CylindricalState(CylindricalVector.ZERO, CylindricalVector.ZERO);
|
||||
|
||||
/**
|
||||
* Creates a state vector.
|
||||
*
|
||||
* @param position the position of one body relative to another.
|
||||
*
|
||||
* @param velocity the velocity, the time derivative of the supplied position.
|
||||
*/
|
||||
public CylindricalState(CylindricalVector position, CylindricalVector velocity) {
|
||||
super(position, velocity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "UnwritableCylindricalState [position=" + super.getPosition() + ", velocity="
|
||||
+ super.getVelocity() + "]";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package picante.math.coords;
|
||||
|
||||
import static picante.math.PicanteMath.cos;
|
||||
import static picante.math.PicanteMath.sin;
|
||||
import picante.math.vectorspace.MatrixIJK;
|
||||
import picante.math.vectorspace.UnwritableMatrixIJK;
|
||||
import picante.math.vectorspace.UnwritableVectorIJK;
|
||||
|
||||
class CylindricalToCartesianBasisTransformation implements Transformation<CylindricalVector> {
|
||||
|
||||
/**
|
||||
* return
|
||||
*
|
||||
* <pre>
|
||||
* .- -.
|
||||
* | cos(long) -sin(long) 0 |
|
||||
* | |
|
||||
* | sin(long) cos(long) 0 |
|
||||
* | |
|
||||
* | 0 0 1 |
|
||||
* `- -'
|
||||
* </pre>
|
||||
*
|
||||
*/
|
||||
@Override
|
||||
public MatrixIJK getTransformation(CylindricalVector coordPosition, MatrixIJK buffer) {
|
||||
|
||||
double lon = coordPosition.getLongitude();
|
||||
|
||||
double cosLon = cos(lon);
|
||||
double sinLon = sin(lon);
|
||||
|
||||
double j_DX_DR = cosLon;
|
||||
double j_DY_DR = sinLon;
|
||||
double j_DZ_DR = 0.0;
|
||||
|
||||
double j_DX_DLON = -sinLon;
|
||||
double j_DY_DLON = cosLon;
|
||||
double j_DZ_DLON = 0.0;
|
||||
|
||||
double j_DX_DZ = 0.0;
|
||||
double j_DY_DZ = 0.0;
|
||||
double j_DZ_DZ = 1.0;
|
||||
|
||||
return buffer.setTo(j_DX_DR, j_DY_DR, j_DZ_DR, j_DX_DLON, j_DY_DLON, j_DZ_DLON, j_DX_DZ,
|
||||
j_DY_DZ, j_DZ_DZ);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MatrixIJK getInverseTransformation(CylindricalVector coordPosition, MatrixIJK buffer) {
|
||||
return getTransformation(coordPosition, buffer).invort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnwritableVectorIJK mxv(UnwritableMatrixIJK jacobian, CylindricalVector coordValue) {
|
||||
return jacobian.mxv(coordValue.getVectorIJK());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CylindricalVector mxv(UnwritableMatrixIJK inverseTransformation,
|
||||
UnwritableVectorIJK cartVelocity) {
|
||||
UnwritableVectorIJK vect = inverseTransformation.mxv(cartVelocity);
|
||||
return new CylindricalVector(vect.getI(), vect.getJ(), vect.getK());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package picante.math.coords;
|
||||
|
||||
import static picante.math.PicanteMath.cos;
|
||||
import static picante.math.PicanteMath.sin;
|
||||
import picante.math.vectorspace.MatrixIJK;
|
||||
import picante.math.vectorspace.UnwritableMatrixIJK;
|
||||
import picante.math.vectorspace.UnwritableVectorIJK;
|
||||
|
||||
class CylindricalToCartesianJacobian implements Transformation<CylindricalVector> {
|
||||
|
||||
/**
|
||||
* return
|
||||
*
|
||||
* <pre>
|
||||
* .- -.
|
||||
* | dx/dr dx/dlong dx/dz |
|
||||
* | |
|
||||
* | dy/dr dy/dlong dy/dz |
|
||||
* | |
|
||||
* | dz/dr dz/dlong dz/dz |
|
||||
* `- -'
|
||||
*
|
||||
* .- -.
|
||||
* | cos(long) -sin(long)*r 0 |
|
||||
* | |
|
||||
* | sin(long) cos(long)*r 0 |
|
||||
* | |
|
||||
* | 0 0 1 |
|
||||
* `- -'
|
||||
* </pre>
|
||||
*
|
||||
*/
|
||||
@Override
|
||||
public MatrixIJK getTransformation(CylindricalVector coordPosition, MatrixIJK buffer) {
|
||||
/*
|
||||
* from SPICE's routine in drdcyl.f
|
||||
*
|
||||
* JACOBI (DX,DR) = DCOS( LONG )
|
||||
*
|
||||
* JACOBI (DY,DR) = DSIN( LONG )
|
||||
*
|
||||
* JACOBI (DZ,DR) = 0.0D0
|
||||
*
|
||||
*
|
||||
* JACOBI (DX,DLON) = -DSIN( LONG ) * R
|
||||
*
|
||||
* JACOBI (DY,DLON) = DCOS( LONG ) * R
|
||||
*
|
||||
* JACOBI (DZ,DLON) = 0.0D0
|
||||
*
|
||||
*
|
||||
* JACOBI (DX,DZ) = 0.0D0
|
||||
*
|
||||
* JACOBI (DY,DZ) = 0.0D0
|
||||
*
|
||||
* JACOBI (DZ,DZ) = 1.0D0
|
||||
*/
|
||||
|
||||
double r = coordPosition.getCylindricalRadius();
|
||||
double lon = coordPosition.getLongitude();
|
||||
|
||||
double cosLon = cos(lon);
|
||||
double sinLon = sin(lon);
|
||||
|
||||
double j_DX_DR = cosLon;
|
||||
double j_DY_DR = sinLon;
|
||||
double j_DZ_DR = 0.0;
|
||||
|
||||
double j_DX_DLON = -sinLon * r;
|
||||
double j_DY_DLON = cosLon * r;
|
||||
double j_DZ_DLON = 0.0;
|
||||
|
||||
double j_DX_DZ = 0.0;
|
||||
double j_DY_DZ = 0.0;
|
||||
double j_DZ_DZ = 1.0;
|
||||
|
||||
return buffer.setTo(j_DX_DR, j_DY_DR, j_DZ_DR, j_DX_DLON, j_DY_DLON, j_DZ_DLON, j_DX_DZ,
|
||||
j_DY_DZ, j_DZ_DZ);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MatrixIJK getInverseTransformation(CylindricalVector coordPosition, MatrixIJK buffer) {
|
||||
try {
|
||||
return getTransformation(coordPosition, buffer).invort();
|
||||
} catch (UnsupportedOperationException e) {
|
||||
throw new PointOnAxisException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnwritableVectorIJK mxv(UnwritableMatrixIJK jacobian, CylindricalVector coordVelocity) {
|
||||
return jacobian.mxv(coordVelocity.getVectorIJK());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CylindricalVector mxv(UnwritableMatrixIJK inverseJacobian,
|
||||
UnwritableVectorIJK cartVelocity) {
|
||||
UnwritableVectorIJK vect = inverseJacobian.mxv(cartVelocity);
|
||||
return new CylindricalVector(vect.getI(), vect.getJ(), vect.getK());
|
||||
}
|
||||
|
||||
}
|
||||
51
src/main/java/picante/math/coords/CylindricalVector.java
Normal file
51
src/main/java/picante/math/coords/CylindricalVector.java
Normal file
@@ -0,0 +1,51 @@
|
||||
package picante.math.coords;
|
||||
|
||||
/**
|
||||
* A class representing a vector in the cylindrical coordinate system.
|
||||
*
|
||||
* @author G.K.Stephens
|
||||
*
|
||||
*/
|
||||
public final class CylindricalVector extends AbstractVector {
|
||||
|
||||
/**
|
||||
* The ZERO vector.
|
||||
*/
|
||||
public static final CylindricalVector ZERO = new CylindricalVector(0, 0, 0);
|
||||
|
||||
public CylindricalVector(double cylindricalRadius, double longInRadians, double height) {
|
||||
super(cylindricalRadius, longInRadians, height);
|
||||
}
|
||||
|
||||
// public CylindricalVector(double[] data) {
|
||||
// super(data);
|
||||
// }
|
||||
|
||||
/**
|
||||
* @return the cylindrical radius (often denoted as r or ρ)
|
||||
*/
|
||||
public final double getCylindricalRadius() {
|
||||
return super.getI();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the longitude
|
||||
*/
|
||||
public final double getLongitude() {
|
||||
return super.getJ();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the height (often denoted as z)
|
||||
*/
|
||||
public final double getHeight() {
|
||||
return super.getK();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CylindricalVector [cylindricalRadius: " + getCylindricalRadius() + ", longitude: "
|
||||
+ getLongitude() + ", height: " + getHeight() + "]";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package picante.math.coords;
|
||||
|
||||
/**
|
||||
* A container class for a cylindrical coordinate <img src="./doc-files/cylCoord.png"/> and vector
|
||||
* field value <img src="./doc-files/cylVect.png"/> at that coordinate.
|
||||
*
|
||||
* @author G.K.Stephens
|
||||
*
|
||||
*/
|
||||
public final class CylindricalVectorFieldValue extends AbstractVectorFieldValue<CylindricalVector> {
|
||||
|
||||
/**
|
||||
* @param position a cylindrical coordinate <img src="./doc-files/cylCoord.png"/>
|
||||
* @param value a cylindrical vector field value <img src="./doc-files/cylVect.png"/> at that
|
||||
* coordinate
|
||||
*/
|
||||
public CylindricalVectorFieldValue(CylindricalVector position, CylindricalVector value) {
|
||||
super(position, value);
|
||||
}
|
||||
|
||||
}
|
||||
116
src/main/java/picante/math/coords/LatitudinalCoordConverter.java
Normal file
116
src/main/java/picante/math/coords/LatitudinalCoordConverter.java
Normal file
@@ -0,0 +1,116 @@
|
||||
package picante.math.coords;
|
||||
|
||||
import static picante.math.PicanteMath.abs;
|
||||
import static picante.math.PicanteMath.atan2;
|
||||
import static picante.math.PicanteMath.cos;
|
||||
import static picante.math.PicanteMath.max;
|
||||
import static picante.math.PicanteMath.sin;
|
||||
import static picante.math.PicanteMath.sqrt;
|
||||
import picante.math.vectorspace.UnwritableVectorIJK;
|
||||
|
||||
class LatitudinalCoordConverter extends AbstractCoordConverter<LatitudinalVector> {
|
||||
|
||||
private final static LatitudinalToCartesianJacobian JACOBIAN =
|
||||
new LatitudinalToCartesianJacobian();
|
||||
|
||||
public LatitudinalCoordConverter() {
|
||||
super(JACOBIAN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LatitudinalVector toCoordinate(UnwritableVectorIJK cartesian) {
|
||||
/*
|
||||
* From the SPICE routine reclat.f.
|
||||
*
|
||||
* BIG = MAX ( DABS(RECTAN(1)), DABS(RECTAN(2)), DABS(RECTAN(3)) )
|
||||
*
|
||||
* IF ( BIG .GT. 0 ) THEN
|
||||
*
|
||||
* X = RECTAN(1) / BIG Y = RECTAN(2) / BIG Z = RECTAN(3) / BIG
|
||||
*
|
||||
* RADIUS = BIG * DSQRT (X*X + Y*Y + Z*Z)
|
||||
*
|
||||
* LAT = DATAN2 ( Z, DSQRT(X*X + Y*Y) )
|
||||
*
|
||||
* X = RECTAN(1) Y = RECTAN(2)
|
||||
*
|
||||
* IF (X.EQ.0.D0 .AND. Y.EQ.0.D0) THEN LONG = 0.D0 ELSE LONG = DATAN2 (Y,X) END IF
|
||||
*
|
||||
* ELSE RADIUS = 0.0D0 LAT = 0.D0 LONG = 0.D0 END IF
|
||||
*/
|
||||
|
||||
double x = cartesian.getI();
|
||||
double y = cartesian.getJ();
|
||||
double z = cartesian.getK();
|
||||
|
||||
double radius = 0;
|
||||
double latitude = 0;
|
||||
double longitude = 0;
|
||||
|
||||
double big = max(max(abs(x), abs(y)), abs(z));
|
||||
if (big > 0) {
|
||||
x /= big;
|
||||
y /= big;
|
||||
z /= big;
|
||||
|
||||
radius = big * sqrt(x * x + y * y + z * z);
|
||||
latitude = atan2(z, sqrt(x * x + y * y));
|
||||
|
||||
x = cartesian.getI();
|
||||
y = cartesian.getJ();
|
||||
|
||||
if ((x == 0) && (y == 0)) {
|
||||
longitude = 0;
|
||||
} else {
|
||||
longitude = atan2(y, x);
|
||||
}
|
||||
} else {
|
||||
radius = 0;
|
||||
latitude = 0;
|
||||
longitude = 0;
|
||||
}
|
||||
|
||||
return new LatitudinalVector(radius, latitude, longitude);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnwritableVectorIJK toCartesian(LatitudinalVector coordinate) {
|
||||
|
||||
/*
|
||||
* From the SPICE routine latrec.f.
|
||||
*
|
||||
* X = RADIUS * DCOS(LONG) * DCOS(LAT)
|
||||
*
|
||||
* Y = RADIUS * DSIN(LONG) * DCOS(LAT)
|
||||
*
|
||||
* Z = RADIUS * DSIN(LAT)
|
||||
*/
|
||||
|
||||
double i =
|
||||
coordinate.getRadius() * cos(coordinate.getLongitude()) * cos(coordinate.getLatitude());
|
||||
double j =
|
||||
coordinate.getRadius() * sin(coordinate.getLongitude()) * cos(coordinate.getLatitude());
|
||||
double k = coordinate.getRadius() * sin(coordinate.getLatitude());
|
||||
|
||||
return new UnwritableVectorIJK(i, j, k);
|
||||
}
|
||||
|
||||
@Override
|
||||
State<LatitudinalVector> construct(LatitudinalVector position, LatitudinalVector velocity) {
|
||||
return new LatitudinalState(position, velocity);
|
||||
}
|
||||
|
||||
/**
|
||||
* This was necessary, Math.cos(Math.PI/2.0) does not give a number close enough to zero to get
|
||||
* the make the invort method throw this exception, this is not how this is handled in the
|
||||
* Spherical case
|
||||
*/
|
||||
@Override
|
||||
public State<LatitudinalVector> toCoordinate(State<UnwritableVectorIJK> cartesian) {
|
||||
if (cartesian.getPosition().getI() == 0.0 && cartesian.getPosition().getJ() == 0.0) {
|
||||
throw new PointOnAxisException();
|
||||
}
|
||||
return super.toCoordinate(cartesian);
|
||||
}
|
||||
|
||||
}
|
||||
22
src/main/java/picante/math/coords/LatitudinalState.java
Normal file
22
src/main/java/picante/math/coords/LatitudinalState.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package picante.math.coords;
|
||||
|
||||
public final class LatitudinalState extends AbstractState<LatitudinalVector> {
|
||||
|
||||
/**
|
||||
* The zero state vector: zero position and zero velocity components.
|
||||
*/
|
||||
public static final LatitudinalState ZERO =
|
||||
new LatitudinalState(LatitudinalVector.ZERO, LatitudinalVector.ZERO);
|
||||
|
||||
public LatitudinalState(LatitudinalVector position, LatitudinalVector velocity) {
|
||||
super(position, velocity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" + super.getPosition().getRadius() + "," + super.getPosition().getLatitude() + ","
|
||||
+ super.getPosition().getLongitude() + "; " + super.getVelocity().getRadius() + ","
|
||||
+ super.getVelocity().getLatitude() + "," + super.getVelocity().getLongitude() + "]";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package picante.math.coords;
|
||||
|
||||
import static picante.math.PicanteMath.cos;
|
||||
import static picante.math.PicanteMath.sin;
|
||||
import picante.math.vectorspace.MatrixIJK;
|
||||
import picante.math.vectorspace.UnwritableMatrixIJK;
|
||||
import picante.math.vectorspace.UnwritableVectorIJK;
|
||||
|
||||
class LatitudinalToCartesianJacobian implements Transformation<LatitudinalVector> {
|
||||
|
||||
@Override
|
||||
public MatrixIJK getTransformation(LatitudinalVector coordPosition, MatrixIJK buffer) {
|
||||
/*
|
||||
* from SPICE's routine in drdlat.f:
|
||||
*
|
||||
* JACOBI (DX,DR) = DCOS( LONG ) * DCOS( LAT )
|
||||
*
|
||||
* JACOBI (DY,DR) = DSIN( LONG ) * DCOS( LAT )
|
||||
*
|
||||
* JACOBI (DZ,DR) = DSIN( LAT )
|
||||
*
|
||||
*
|
||||
* JACOBI (DX,DLON) = -R * DSIN( LONG ) * DCOS( LAT )
|
||||
*
|
||||
* JACOBI (DY,DLON) = R * DCOS( LONG ) * DCOS( LAT )
|
||||
*
|
||||
* JACOBI (DZ,DLON) = 0.0D0
|
||||
*
|
||||
*
|
||||
* JACOBI (DX,DLAT) = -R * DCOS( LONG ) * DSIN( LAT )
|
||||
*
|
||||
* JACOBI (DY,DLAT) = -R * DSIN( LONG ) * DSIN( LAT )
|
||||
*
|
||||
* JACOBI (DZ,DLAT) = R * DCOS( LAT )
|
||||
*/
|
||||
|
||||
double r = coordPosition.getRadius();
|
||||
double lat = coordPosition.getLatitude();
|
||||
double lon = coordPosition.getLongitude();
|
||||
|
||||
double cosLat = cos(lat);
|
||||
double sinLat = sin(lat);
|
||||
|
||||
double cosLong = cos(lon);
|
||||
double sinLong = sin(lon);
|
||||
|
||||
double xByR = cosLong * cosLat;
|
||||
double yByR = sinLong * cosLat;
|
||||
double zByR = sinLat;
|
||||
|
||||
double xByLat = -r * cosLong * sinLat;
|
||||
double yByLat = -r * sinLong * sinLat;
|
||||
double zByLat = r * cosLat;
|
||||
|
||||
double xByLong = -r * sinLong * cosLat;
|
||||
double yByLong = r * cosLong * cosLat;
|
||||
double zByLong = 0;
|
||||
return buffer.setTo(xByR, yByR, zByR, xByLat, yByLat, zByLat, xByLong, yByLong, zByLong);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MatrixIJK getInverseTransformation(LatitudinalVector coordPosition, MatrixIJK buffer) {
|
||||
try {
|
||||
return getTransformation(coordPosition, buffer).invort();
|
||||
} catch (UnsupportedOperationException e) {
|
||||
throw new PointOnAxisException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnwritableVectorIJK mxv(UnwritableMatrixIJK jacobian, LatitudinalVector coordVelocity) {
|
||||
return jacobian.mxv(coordVelocity.getVectorIJK());
|
||||
}
|
||||
|
||||
@Override
|
||||
public LatitudinalVector mxv(UnwritableMatrixIJK inverseJacobian,
|
||||
UnwritableVectorIJK cartVelocity) {
|
||||
UnwritableVectorIJK vect = inverseJacobian.mxv(cartVelocity);
|
||||
return new LatitudinalVector(vect.getI(), vect.getJ(), vect.getK());
|
||||
}
|
||||
|
||||
}
|
||||
51
src/main/java/picante/math/coords/LatitudinalVector.java
Normal file
51
src/main/java/picante/math/coords/LatitudinalVector.java
Normal file
@@ -0,0 +1,51 @@
|
||||
package picante.math.coords;
|
||||
|
||||
/**
|
||||
* A class representing a vector in the latitudinal coordinate system.
|
||||
*
|
||||
* @author G.K.Stephens
|
||||
*
|
||||
*/
|
||||
public final class LatitudinalVector extends AbstractVector {
|
||||
|
||||
/**
|
||||
* The ZERO vector.
|
||||
*/
|
||||
public static final LatitudinalVector ZERO = new LatitudinalVector(0, 0, 0);
|
||||
|
||||
public LatitudinalVector(double radius, double latInRadians, double longInRadians) {
|
||||
super(radius, latInRadians, longInRadians);
|
||||
}
|
||||
|
||||
// public LatitudinalVector(double[] data) {
|
||||
// super(data);
|
||||
// }
|
||||
|
||||
/**
|
||||
* @return the radius
|
||||
*/
|
||||
public double getRadius() {
|
||||
return super.getI();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the latitude
|
||||
*/
|
||||
public double getLatitude() {
|
||||
return super.getJ();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the longitude
|
||||
*/
|
||||
public double getLongitude() {
|
||||
return super.getK();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LatitudinalVector [radius: " + getRadius() + ", latitude: " + getLatitude()
|
||||
+ ", longitude: " + getLongitude() + "]";
|
||||
}
|
||||
|
||||
}
|
||||
24
src/main/java/picante/math/coords/PointOnAxisException.java
Normal file
24
src/main/java/picante/math/coords/PointOnAxisException.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package picante.math.coords;
|
||||
|
||||
import picante.exceptions.PicanteRuntimeException;
|
||||
|
||||
public class PointOnAxisException extends PicanteRuntimeException {
|
||||
/**
|
||||
* Default serial version UID.
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public PointOnAxisException() {}
|
||||
|
||||
public PointOnAxisException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public PointOnAxisException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public PointOnAxisException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
86
src/main/java/picante/math/coords/PolarCoordConverter.java
Normal file
86
src/main/java/picante/math/coords/PolarCoordConverter.java
Normal file
@@ -0,0 +1,86 @@
|
||||
package picante.math.coords;
|
||||
|
||||
import static picante.math.PicanteMath.abs;
|
||||
import static picante.math.PicanteMath.atan2;
|
||||
import static picante.math.PicanteMath.cos;
|
||||
import static picante.math.PicanteMath.max;
|
||||
import static picante.math.PicanteMath.sin;
|
||||
import static picante.math.PicanteMath.sqrt;
|
||||
import picante.math.vectorspace.UnwritableVectorIJ;
|
||||
|
||||
class PolarCoordConverter extends AbstractCoordConverterIJ<PolarVector> {
|
||||
|
||||
private final static PolarToCartesianJacobian JACOBIAN = new PolarToCartesianJacobian();
|
||||
|
||||
public PolarCoordConverter() {
|
||||
super(JACOBIAN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PolarVector toCoordinate(UnwritableVectorIJ cartesian) {
|
||||
|
||||
/*
|
||||
* From the SPICE routine recsph.f, here is an algorithm for converting to polar polar
|
||||
* coordinates from rectangular coordiantes: C C Store rectangular coordinates in temporary
|
||||
* variables C BIG = MAX ( DABS(RECTAN(1)), DABS(RECTAN(2)), DABS(RECTAN(3)) )
|
||||
*
|
||||
* IF ( BIG .GT. 0 ) THEN
|
||||
*
|
||||
* X = RECTAN(1) / BIG Y = RECTAN(2) / BIG Z = RECTAN(3) / BIG
|
||||
*
|
||||
* R = BIG * DSQRT (X*X + Y*Y + Z*Z)
|
||||
*
|
||||
* COLAT = DATAN2 ( DSQRT(X*X + Y*Y), Z )
|
||||
*
|
||||
* X = RECTAN(1) Y = RECTAN(2)
|
||||
*
|
||||
* IF (X.EQ.0.0D0 .AND. Y.EQ.0.0D0) THEN LONG = 0.0D0 ELSE LONG = DATAN2 (Y,X) END IF
|
||||
*
|
||||
* ELSE R = 0.0D0 COLAT = 0.0D0 LONG = 0.0D0 END IF
|
||||
*
|
||||
* RETURN END
|
||||
*/
|
||||
double x = cartesian.getI();
|
||||
double y = cartesian.getJ();
|
||||
|
||||
double radius = 0;
|
||||
double angle = 0;
|
||||
|
||||
double big = max(abs(x), abs(y));
|
||||
if (big > 0) {
|
||||
x /= big;
|
||||
y /= big;
|
||||
|
||||
radius = big * sqrt(x * x + y * y);
|
||||
angle = atan2(y, x);
|
||||
|
||||
x = cartesian.getI();
|
||||
y = cartesian.getJ();
|
||||
|
||||
} else {
|
||||
radius = 0;
|
||||
angle = 0;
|
||||
}
|
||||
|
||||
return new PolarVector(radius, angle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnwritableVectorIJ toCartesian(PolarVector coordinate) {
|
||||
/*
|
||||
* from the SPICE routine sphrec.f, here is a formula for converting from polar polar
|
||||
* coordinates to rectangular coordinates: X = R * DCOS(LONG) * DSIN(COLAT) Y = R * DSIN(LONG) *
|
||||
* DSIN(COLAT) Z = R * DCOS(COLAT)
|
||||
*/
|
||||
double i = coordinate.getRadius() * cos(coordinate.getAngle());
|
||||
double j = coordinate.getRadius() * sin(coordinate.getAngle());
|
||||
|
||||
return new UnwritableVectorIJ(i, j);
|
||||
}
|
||||
|
||||
@Override
|
||||
PolarState construct(PolarVector position, PolarVector velocity) {
|
||||
return new PolarState(position, velocity);
|
||||
}
|
||||
|
||||
}
|
||||
19
src/main/java/picante/math/coords/PolarState.java
Normal file
19
src/main/java/picante/math/coords/PolarState.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package picante.math.coords;
|
||||
|
||||
public final class PolarState extends AbstractState<PolarVector> {
|
||||
|
||||
/**
|
||||
* The zero state vector: zero position and zero velocity components.
|
||||
*/
|
||||
public static final PolarState ZERO = new PolarState(PolarVector.ZERO, PolarVector.ZERO);
|
||||
|
||||
public PolarState(PolarVector position, PolarVector velocity) {
|
||||
super(position, velocity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" + super.getPosition().getRadius() + "," + super.getPosition().getAngle() + "; "
|
||||
+ super.getVelocity().getRadius() + "," + super.getVelocity().getAngle() + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package picante.math.coords;
|
||||
|
||||
import static picante.math.PicanteMath.cos;
|
||||
import static picante.math.PicanteMath.sin;
|
||||
import picante.math.vectorspace.MatrixIJ;
|
||||
import picante.math.vectorspace.UnwritableMatrixIJ;
|
||||
import picante.math.vectorspace.UnwritableVectorIJ;
|
||||
|
||||
class PolarToCartesianJacobian implements TransformationIJ<PolarVector> {
|
||||
|
||||
@Override
|
||||
public MatrixIJ getTransformation(PolarVector coordPosition, MatrixIJ buffer) {
|
||||
|
||||
double r = coordPosition.getRadius();
|
||||
double angle = coordPosition.getAngle();
|
||||
|
||||
double cosAngle = cos(angle);
|
||||
double sinAngle = sin(angle);
|
||||
|
||||
return buffer.setTo(cosAngle, sinAngle, -r * sinAngle, r * cosAngle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MatrixIJ getInverseTransformation(PolarVector coordPosition, MatrixIJ buffer) {
|
||||
try {
|
||||
return getTransformation(coordPosition, buffer).invort();
|
||||
} catch (UnsupportedOperationException e) {
|
||||
throw new PointOnAxisException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnwritableVectorIJ mxv(UnwritableMatrixIJ jacobian, PolarVector coordVelocity) {
|
||||
return jacobian.mxv(coordVelocity.getVectorIJ());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PolarVector mxv(UnwritableMatrixIJ inverseJacobian, UnwritableVectorIJ cartVelocity) {
|
||||
UnwritableVectorIJ vect = inverseJacobian.mxv(cartVelocity);
|
||||
return new PolarVector(vect.getI(), vect.getJ());
|
||||
}
|
||||
|
||||
}
|
||||
42
src/main/java/picante/math/coords/PolarVector.java
Normal file
42
src/main/java/picante/math/coords/PolarVector.java
Normal file
@@ -0,0 +1,42 @@
|
||||
package picante.math.coords;
|
||||
|
||||
/**
|
||||
* A class representing a vector in the polar coordinate system.
|
||||
*
|
||||
* @author G.K.Stephens
|
||||
*
|
||||
*/
|
||||
public final class PolarVector extends AbstractVectorIJ {
|
||||
|
||||
/**
|
||||
* The ZERO vector.
|
||||
*/
|
||||
public static final PolarVector ZERO = new PolarVector(0, 0);
|
||||
|
||||
public PolarVector(double radius, double angle) {
|
||||
super(radius, angle);
|
||||
}
|
||||
|
||||
// public PolarVector(double[] data) {
|
||||
// super(data);
|
||||
// }
|
||||
|
||||
/**
|
||||
* @return the radius
|
||||
*/
|
||||
public final double getRadius() {
|
||||
return super.getI();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the angle
|
||||
*/
|
||||
public final double getAngle() {
|
||||
return super.getJ();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PolarVector [radius: " + getRadius() + ", angle: " + getAngle() + "]";
|
||||
}
|
||||
}
|
||||
47
src/main/java/picante/math/coords/RaDecCoordConverter.java
Normal file
47
src/main/java/picante/math/coords/RaDecCoordConverter.java
Normal file
@@ -0,0 +1,47 @@
|
||||
package picante.math.coords;
|
||||
|
||||
import static picante.units.FundamentalPhysicalConstants.TWOPI;
|
||||
import picante.math.vectorspace.UnwritableVectorIJK;
|
||||
|
||||
class RaDecCoordConverter implements CoordConverter<RaDecVector> {
|
||||
|
||||
private final static LatitudinalCoordConverter LAT_CONVERTER = new LatitudinalCoordConverter();
|
||||
|
||||
@Override
|
||||
public RaDecVector toCoordinate(UnwritableVectorIJK cartesian) {
|
||||
|
||||
LatitudinalVector workCoord = LAT_CONVERTER.toCoordinate(cartesian);
|
||||
|
||||
double r = workCoord.getRadius();
|
||||
double lat = workCoord.getLatitude();
|
||||
double lon = workCoord.getLongitude();
|
||||
|
||||
if (lon < 0.0) {
|
||||
lon = lon + TWOPI;
|
||||
}
|
||||
|
||||
return new RaDecVector(r, lon, lat);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnwritableVectorIJK toCartesian(RaDecVector coordinate) {
|
||||
|
||||
LatitudinalVector workCoord = new LatitudinalVector(coordinate.getRadius(),
|
||||
coordinate.getDeclination(), coordinate.getRightAscension());
|
||||
|
||||
return LAT_CONVERTER.toCartesian(workCoord);
|
||||
}
|
||||
|
||||
@Override
|
||||
public State<RaDecVector> toCoordinate(
|
||||
@SuppressWarnings("unused") State<UnwritableVectorIJK> cartesian) {
|
||||
throw new UnsupportedOperationException("not yet supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public State<UnwritableVectorIJK> toCartesian(
|
||||
@SuppressWarnings("unused") State<RaDecVector> coordinate) {
|
||||
throw new UnsupportedOperationException("not yet supported");
|
||||
}
|
||||
|
||||
}
|
||||
51
src/main/java/picante/math/coords/RaDecVector.java
Normal file
51
src/main/java/picante/math/coords/RaDecVector.java
Normal file
@@ -0,0 +1,51 @@
|
||||
package picante.math.coords;
|
||||
|
||||
/**
|
||||
* A class representing a vector in the Celestial Coordinate system.
|
||||
*
|
||||
* @author G.K.Stephens
|
||||
*
|
||||
*/
|
||||
public final class RaDecVector extends AbstractVector {
|
||||
|
||||
/**
|
||||
* The ZERO vector.
|
||||
*/
|
||||
public static final RaDecVector ZERO = new RaDecVector(0, 0, 0);
|
||||
|
||||
public RaDecVector(double radius, double raRadians, double decRadians) {
|
||||
super(radius, raRadians, decRadians);
|
||||
}
|
||||
|
||||
// public RaDecVector(double[] data) {
|
||||
// super(data);
|
||||
// }
|
||||
|
||||
/**
|
||||
* @return the radius
|
||||
*/
|
||||
public double getRadius() {
|
||||
return super.getI();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the right ascension
|
||||
*/
|
||||
public double getRightAscension() {
|
||||
return super.getJ();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the declination
|
||||
*/
|
||||
public double getDeclination() {
|
||||
return super.getK();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RaDecVector [radius: " + getRadius() + ", rightAscension: " + getRightAscension()
|
||||
+ ", declination: " + getDeclination() + "]";
|
||||
}
|
||||
|
||||
}
|
||||
121
src/main/java/picante/math/coords/SphericalCoordConverter.java
Normal file
121
src/main/java/picante/math/coords/SphericalCoordConverter.java
Normal file
@@ -0,0 +1,121 @@
|
||||
package picante.math.coords;
|
||||
|
||||
import static picante.math.PicanteMath.abs;
|
||||
import static picante.math.PicanteMath.atan2;
|
||||
import static picante.math.PicanteMath.cos;
|
||||
import static picante.math.PicanteMath.max;
|
||||
import static picante.math.PicanteMath.sin;
|
||||
import static picante.math.PicanteMath.sqrt;
|
||||
import picante.math.vectorspace.UnwritableVectorIJK;
|
||||
|
||||
class SphericalCoordConverter extends AbstractCoordConverter<SphericalVector> {
|
||||
|
||||
private final static SphericalToCartesianJacobian JACOBIAN = new SphericalToCartesianJacobian();
|
||||
|
||||
public SphericalCoordConverter() {
|
||||
super(JACOBIAN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SphericalVector toCoordinate(UnwritableVectorIJK cartesian) {
|
||||
|
||||
/*
|
||||
* From the SPICE routine recsph.f, here is an algorithm for converting to spherical polar
|
||||
* coordinates from rectangular coordiantes: C C Store rectangular coordinates in temporary
|
||||
* variables C BIG = MAX ( DABS(RECTAN(1)), DABS(RECTAN(2)), DABS(RECTAN(3)) )
|
||||
*
|
||||
* IF ( BIG .GT. 0 ) THEN
|
||||
*
|
||||
* X = RECTAN(1) / BIG Y = RECTAN(2) / BIG Z = RECTAN(3) / BIG
|
||||
*
|
||||
* R = BIG * DSQRT (X*X + Y*Y + Z*Z)
|
||||
*
|
||||
* COLAT = DATAN2 ( DSQRT(X*X + Y*Y), Z )
|
||||
*
|
||||
* X = RECTAN(1) Y = RECTAN(2)
|
||||
*
|
||||
* IF (X.EQ.0.0D0 .AND. Y.EQ.0.0D0) THEN LONG = 0.0D0 ELSE LONG = DATAN2 (Y,X) END IF
|
||||
*
|
||||
* ELSE R = 0.0D0 COLAT = 0.0D0 LONG = 0.0D0 END IF
|
||||
*
|
||||
* RETURN END
|
||||
*/
|
||||
double x = cartesian.getI();
|
||||
double y = cartesian.getJ();
|
||||
double z = cartesian.getK();
|
||||
|
||||
double radius = 0;
|
||||
double colatitude = 0;
|
||||
double longitude = 0;
|
||||
|
||||
double big = max(max(abs(x), abs(y)), abs(z));
|
||||
if (big > 0) {
|
||||
x /= big;
|
||||
y /= big;
|
||||
z /= big;
|
||||
|
||||
radius = big * sqrt(x * x + y * y + z * z);
|
||||
colatitude = atan2(sqrt(x * x + y * y), z);
|
||||
|
||||
x = cartesian.getI();
|
||||
y = cartesian.getJ();
|
||||
|
||||
if ((x == 0) && (y == 0)) {
|
||||
longitude = 0;
|
||||
} else {
|
||||
longitude = atan2(y, x);
|
||||
}
|
||||
} else {
|
||||
radius = 0;
|
||||
colatitude = 0;
|
||||
longitude = 0;
|
||||
}
|
||||
|
||||
return new SphericalVector(radius, colatitude, longitude);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnwritableVectorIJK toCartesian(SphericalVector coordinate) {
|
||||
/*
|
||||
* from the SPICE routine sphrec.f, here is a formula for converting from spherical polar
|
||||
* coordinates to rectangular coordinates:
|
||||
*
|
||||
* X = R * DCOS(LONG) * DSIN(COLAT)
|
||||
*
|
||||
* Y = R * DSIN(LONG) * DSIN(COLAT)
|
||||
*
|
||||
* Z = R * DCOS(COLAT)
|
||||
*/
|
||||
double r = coordinate.getRadius();
|
||||
|
||||
double cosLong = cos(coordinate.getLongitude());
|
||||
double sinLong = sin(coordinate.getLongitude());
|
||||
|
||||
double cosColat = cos(coordinate.getColatitude());
|
||||
double sinColat = sin(coordinate.getColatitude());
|
||||
|
||||
double i = r * cosLong * sinColat;
|
||||
double j = r * sinLong * sinColat;
|
||||
double k = r * cosColat;
|
||||
|
||||
return new UnwritableVectorIJK(i, j, k);
|
||||
}
|
||||
|
||||
@Override
|
||||
State<SphericalVector> construct(SphericalVector position, SphericalVector velocity) {
|
||||
return new SphericalState(position, velocity);
|
||||
}
|
||||
|
||||
/**
|
||||
* This was necessary, it is not always a number close enough to zero to get the make the invort
|
||||
* method throw this exception
|
||||
*/
|
||||
@Override
|
||||
public State<SphericalVector> toCoordinate(State<UnwritableVectorIJK> cartesian) {
|
||||
if (cartesian.getPosition().getI() == 0.0 && cartesian.getPosition().getJ() == 0.0) {
|
||||
throw new PointOnAxisException();
|
||||
}
|
||||
return super.toCoordinate(cartesian);
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user