- * Gzip-compressed files are supported.
+ * All {@link DataProvidersManager#addFilter(DataFilter) registered}
+ * {@link DataFilter filters} are applied.
*
*
* Zip archives entries are supported recursively.
diff --git a/src/main/java/org/orekit/data/DataProvider.java b/src/main/java/org/orekit/data/DataProvider.java
index b515ca54be71466d14ed1199f892aca5d7301503..1ad066fcd6dd0d0c1dad86162036b561a1e37d85 100644
--- a/src/main/java/org/orekit/data/DataProvider.java
+++ b/src/main/java/org/orekit/data/DataProvider.java
@@ -31,7 +31,7 @@ import java.util.regex.Pattern;
* providers manager singleton}, or to let this manager use its default
* configuration. Once registered, they will be used automatically whenever
* some data needs to be loaded. This allow high level applications developers
- * to customize Orekit data loading mechanism and get a tighter intergation of
+ * to customize Orekit data loading mechanism and get a tighter integration of
* the library within their application.
*
* @see DataLoader
diff --git a/src/main/java/org/orekit/data/DataProvidersManager.java b/src/main/java/org/orekit/data/DataProvidersManager.java
index cc81c27880bf370a89af4e9cda8c1689e5bd7e71..b9e66ddd7f8eefdc84e4a5d631cf8c097caeee2f 100644
--- a/src/main/java/org/orekit/data/DataProvidersManager.java
+++ b/src/main/java/org/orekit/data/DataProvidersManager.java
@@ -30,6 +30,7 @@ import java.util.regex.Pattern;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
+import org.orekit.gnss.HatanakaCompressFilter;
/** Singleton class managing all supported {@link DataProvider data providers}.
@@ -102,6 +103,7 @@ public class DataProvidersManager {
// set up predefined filters
addFilter(new GzipFilter());
addFilter(new UnixCompressFilter());
+ addFilter(new HatanakaCompressFilter());
predefinedFilters = filters.size();
diff --git a/src/main/java/org/orekit/data/DirectoryCrawler.java b/src/main/java/org/orekit/data/DirectoryCrawler.java
index c20f371d8c83c9247db4bd4cfb28af54d815eec2..6008c68b180a40e326df7816d4bf4c5249ec47b5 100644
--- a/src/main/java/org/orekit/data/DirectoryCrawler.java
+++ b/src/main/java/org/orekit/data/DirectoryCrawler.java
@@ -39,7 +39,8 @@ import org.orekit.errors.OrekitMessages;
* files are checked for loading.
*
*
- * Gzip-compressed files are supported.
+ * All {@link DataProvidersManager#addFilter(DataFilter) registered}
+ * {@link DataFilter filters} are applied.
*
*
* Zip archives entries are supported recursively.
diff --git a/src/main/java/org/orekit/data/FilesListCrawler.java b/src/main/java/org/orekit/data/FilesListCrawler.java
new file mode 100644
index 0000000000000000000000000000000000000000..0153fead38ec26b05533812341e81808ebc65bef
--- /dev/null
+++ b/src/main/java/org/orekit/data/FilesListCrawler.java
@@ -0,0 +1,72 @@
+/* Copyright 2002-2019 CS Systèmes d'Information
+ * Licensed to CS Systèmes d'Information (CS) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.data;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+
+/** Provider for data files in an explicit list.
+
+ *
{
+
+ /** Build a data classpath crawler.
+ * The default timeout is set to 10 seconds.
+ * @param inputs list of input files
+ */
+ public FilesListCrawler(final File... inputs) {
+ super(inputs);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected String getCompleteName(final File input) {
+ return input.getPath();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected String getBaseName(final File input) {
+ return input.getName();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected ZipJarCrawler getZipJarCrawler(final File input) {
+ return new ZipJarCrawler(input);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected InputStream getStream(final File input) throws IOException {
+ return new FileInputStream(input);
+ }
+
+}
diff --git a/src/main/java/org/orekit/data/FundamentalNutationArguments.java b/src/main/java/org/orekit/data/FundamentalNutationArguments.java
index 9a5de66de52592ae73ecf6c01a772d83348578ac..9c8612bce38dab6b50f4fde46db6f24ee75d3a46 100644
--- a/src/main/java/org/orekit/data/FundamentalNutationArguments.java
+++ b/src/main/java/org/orekit/data/FundamentalNutationArguments.java
@@ -21,6 +21,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Serializable;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -179,7 +180,7 @@ public class FundamentalNutationArguments implements Serializable {
final DefinitionParser definitionParser = new DefinitionParser();
// setup the reader
- final BufferedReader reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
+ final BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8));
int lineNumber = 0;
// look for the reference date and the 14 polynomials
diff --git a/src/main/java/org/orekit/data/NetworkCrawler.java b/src/main/java/org/orekit/data/NetworkCrawler.java
index f2f5f80a2eace2506eac285843fe80467a9410f8..1811ed8d278314e53185386a1c5e63401bb45fe2 100644
--- a/src/main/java/org/orekit/data/NetworkCrawler.java
+++ b/src/main/java/org/orekit/data/NetworkCrawler.java
@@ -22,10 +22,6 @@ import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
-import java.text.ParseException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.regex.Pattern;
import org.hipparchus.exception.DummyLocalizable;
import org.orekit.errors.OrekitException;
@@ -59,7 +55,8 @@ import org.orekit.errors.OrekitException;
*
*
*
- * Gzip-compressed files are supported.
+ * All {@link DataProvidersManager#addFilter(DataFilter) registered}
+ * {@link DataFilter filters} are applied.
*
*
* Zip archives entries are supported recursively.
@@ -71,27 +68,18 @@ import org.orekit.errors.OrekitException;
* @see DataProvidersManager
* @author Luc Maisonobe
*/
-public class NetworkCrawler implements DataProvider {
-
- /** URLs list. */
- private final List urls;
+public class NetworkCrawler extends AbstractListCrawler {
/** Connection timeout (milliseconds). */
private int timeout;
/** Build a data classpath crawler.
* The default timeout is set to 10 seconds.
- * @param urls list of data file URLs
+ * @param inputs list of input file URLs
*/
- public NetworkCrawler(final URL... urls) {
-
- this.urls = new ArrayList();
- for (final URL url : urls) {
- this.urls.add(url);
- }
-
+ public NetworkCrawler(final URL... inputs) {
+ super(inputs);
timeout = 10000;
-
}
/** Set the timeout for connection.
@@ -102,66 +90,31 @@ public class NetworkCrawler implements DataProvider {
}
/** {@inheritDoc} */
- public boolean feed(final Pattern supported, final DataLoader visitor) {
-
+ @Override
+ protected String getCompleteName(final URL input) {
try {
- OrekitException delayedException = null;
- boolean loaded = false;
- for (URL url : urls) {
- try {
-
- if (visitor.stillAcceptsData()) {
- final String name = url.toURI().toString();
- final String fileName = new File(url.getPath()).getName();
- if (ZIP_ARCHIVE_PATTERN.matcher(fileName).matches()) {
-
- // browse inside the zip/jar file
- new ZipJarCrawler(url).feed(supported, visitor);
- loaded = true;
-
- } else {
-
- // apply all registered filters
- NamedData data = new NamedData(fileName, () -> getStream(url));
- data = DataProvidersManager.getInstance().applyAllFilters(data);
-
- if (supported.matcher(data.getName()).matches()) {
- // visit the current file
- try (InputStream input = data.getStreamOpener().openStream()) {
- visitor.loadData(input, name);
- loaded = true;
- }
- }
-
- }
- }
-
- } catch (OrekitException oe) {
- // maybe the next path component will be able to provide data
- // wait until all components have been tried
- delayedException = oe;
- }
- }
-
- if (!loaded && delayedException != null) {
- throw delayedException;
- }
-
- return loaded;
-
- } catch (URISyntaxException | IOException | ParseException e) {
- throw new OrekitException(e, new DummyLocalizable(e.getMessage()));
+ return input.toURI().toString();
+ } catch (URISyntaxException ue) {
+ throw new OrekitException(ue, new DummyLocalizable(ue.getMessage()));
}
+ }
+ /** {@inheritDoc} */
+ @Override
+ protected String getBaseName(final URL input) {
+ return new File(input.getPath()).getName();
}
- /** Get the stream to read from the remote URL.
- * @param url url to read from
- * @return stream to read the content of the URL
- * @throws IOException if the URL cannot be opened for reading
- */
- private InputStream getStream(final URL url) throws IOException {
- final URLConnection connection = url.openConnection();
+ /** {@inheritDoc} */
+ @Override
+ protected ZipJarCrawler getZipJarCrawler(final URL input) {
+ return new ZipJarCrawler(input);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected InputStream getStream(final URL input) throws IOException {
+ final URLConnection connection = input.openConnection();
connection.setConnectTimeout(timeout);
return connection.getInputStream();
}
diff --git a/src/main/java/org/orekit/data/PoissonSeriesParser.java b/src/main/java/org/orekit/data/PoissonSeriesParser.java
index bc095591c9491534a3b37897f3ff3362f01fbe0c..d16b5933994beece6dc97f4e21acd12a3c669431 100644
--- a/src/main/java/org/orekit/data/PoissonSeriesParser.java
+++ b/src/main/java/org/orekit/data/PoissonSeriesParser.java
@@ -20,6 +20,7 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@@ -524,7 +525,7 @@ public class PoissonSeriesParser {
try {
// setup the reader
- final BufferedReader reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
+ final BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8));
int lineNumber = 0;
int expectedIndex = -1;
int nTerms = -1;
diff --git a/src/main/java/org/orekit/data/SimpleTimeStampedTableParser.java b/src/main/java/org/orekit/data/SimpleTimeStampedTableParser.java
index 79f674b74c51bcd29dc2de5691ef7a46d2328773..cfe55cb02c9f2fc6309e6d07515efaae7d75f63e 100644
--- a/src/main/java/org/orekit/data/SimpleTimeStampedTableParser.java
+++ b/src/main/java/org/orekit/data/SimpleTimeStampedTableParser.java
@@ -20,6 +20,7 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
@@ -93,7 +94,7 @@ public class SimpleTimeStampedTableParser {
try {
// setup the reader
- final BufferedReader reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
+ final BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8));
final List table = new ArrayList();
diff --git a/src/main/java/org/orekit/data/UnixCompressFilter.java b/src/main/java/org/orekit/data/UnixCompressFilter.java
index 0bb694130e908a80bce2221f7d7f13b3d73dca65..5eb95c5564ba8100e22c067c83ae901156673b3a 100644
--- a/src/main/java/org/orekit/data/UnixCompressFilter.java
+++ b/src/main/java/org/orekit/data/UnixCompressFilter.java
@@ -75,6 +75,9 @@ public class UnixCompressFilter implements DataFilter {
/** Underlying compressed stream. */
private final InputStream input;
+ /** Indicator for end of input. */
+ private boolean endOfInput;
+
/** Common sequences table. */
private final UncompressedSequence[] table;
@@ -119,9 +122,9 @@ public class UnixCompressFilter implements DataFilter {
ZInputStream(final String name, final InputStream input)
throws IOException {
- this.name = name;
- this.input = input;
-
+ this.name = name;
+ this.input = input;
+ this.endOfInput = false;
// check header
if (input.read() != MAGIC_HEADER_1 || input.read() != MAGIC_HEADER_2) {
@@ -232,7 +235,14 @@ public class UnixCompressFilter implements DataFilter {
if (previousSequence != null && available < table.length) {
// update the table with the next uncompressed byte appended to previous sequence
- final byte nextByte = (key == available) ? previousSequence.getByte(0) : table[key].getByte(0);
+ final byte nextByte;
+ if (key == available) {
+ nextByte = previousSequence.getByte(0);
+ } else if (table[key] != null) {
+ nextByte = table[key].getByte(0);
+ } else {
+ throw new OrekitIOException(OrekitMessages.CORRUPTED_FILE, name);
+ }
table[available++] = new UncompressedSequence(previousSequence, nextByte);
if (available > currentMaxKey && currentWidth < maxWidth) {
// we need to increase the key size
@@ -258,8 +268,9 @@ public class UnixCompressFilter implements DataFilter {
public int read() throws IOException {
if (currentSequence == null) {
- if (!selectNext()) {
+ if (endOfInput || !selectNext()) {
// we have reached end of data
+ endOfInput = true;
return -1;
}
}
diff --git a/src/main/java/org/orekit/data/ZipJarCrawler.java b/src/main/java/org/orekit/data/ZipJarCrawler.java
index c671f446042c9286dc5eca9fbaa27291e62ab638..47c0dee194099e54c4fb1c49ef7636e8a19962f9 100644
--- a/src/main/java/org/orekit/data/ZipJarCrawler.java
+++ b/src/main/java/org/orekit/data/ZipJarCrawler.java
@@ -46,7 +46,8 @@ import org.orekit.errors.OrekitException;
* loader, all of them will be loaded.
*
*
- * Gzip-compressed files are supported.
+ * All {@link DataProvidersManager#addFilter(DataFilter) registered}
+ * {@link DataFilter filters} are applied.
*
*
* Zip archives entries are supported recursively.
diff --git a/src/main/java/org/orekit/errors/OrekitMessages.java b/src/main/java/org/orekit/errors/OrekitMessages.java
index b5f9b7dcf7c6824ae6a2e516fd9037b90296fbc6..9d4dc9854477eed4edb654154a10438db847f642 100644
--- a/src/main/java/org/orekit/errors/OrekitMessages.java
+++ b/src/main/java/org/orekit/errors/OrekitMessages.java
@@ -21,6 +21,7 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
+import java.nio.charset.StandardCharsets;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.PropertyResourceBundle;
@@ -232,11 +233,20 @@ public enum OrekitMessages implements Localizable {
INVALID_SATELLITE_SYSTEM("invalid satellite system {0}"),
NO_TEC_DATA_IN_FILE_FOR_DATE("IONEX file {0} does not contain TEC data for date {1}"),
INCONSISTENT_NUMBER_OF_TEC_MAPS_IN_FILE("number of maps {0} is inconsistent with header specification: {1}"),
+ NO_LATITUDE_LONGITUDE_BONDARIES_IN_IONEX_HEADER("file {0} does not contain latitude or longitude bondaries in its header section"),
+ NO_EPOCH_IN_IONEX_HEADER("file {0} does not contain epoch of first or last map in its header section"),
ITRF_VERSIONS_PREFIX_ONLY("The first column of itrf-versions.conf is a plain " +
"prefix that is matched against the name of each loaded file. It should " +
"not contain any regular expression syntax or directory components, i.e. " +
"\"/\" or \"\\\". Actual value: \"{0}\"."),
- CANNOT_COMPUTE_AIMING_AT_SINGULAR_POINT("cannot compute aiming direction at singular point: latitude = {0}, longitude = {1}");
+ CANNOT_COMPUTE_AIMING_AT_SINGULAR_POINT("cannot compute aiming direction at singular point: latitude = {0}, longitude = {1}"),
+ STEC_INTEGRATION_DID_NOT_CONVERGE("STEC integration did not converge"),
+ MODIP_GRID_NOT_LOADED("MODIP grid not be loaded from {0}"),
+ NEQUICK_F2_FM3_NOT_LOADED("NeQuick coefficient f2 or fm3 not be loaded from {0}"),
+ NOT_A_SUPPORTED_HATANAKA_COMPRESSED_FILE("file {0} is not a supported Hatanaka-compressed file"),
+ INVALID_MEASUREMENT_TYPES_FOR_COMBINATION_OF_MEASUREMENTS("invalid measurement types {0} and {1} for the combination of measurements {2}"),
+ INCOMPATIBLE_FREQUENCIES_FOR_COMBINATION_OF_MEASUREMENTS("frequencies {0} and {1} are incompatibles for the {2} combination"),
+ NON_CHRONOLOGICAL_DATES_FOR_OBSERVATIONS("observations {0} and {1} are not in chronological dates");
// CHECKSTYLE: resume JavadocVariable check
@@ -319,7 +329,7 @@ public enum OrekitMessages implements Localizable {
if (stream != null) {
try {
// Only this line is changed to make it to read properties files as UTF-8.
- bundle = new PropertyResourceBundle(new InputStreamReader(stream, "UTF-8"));
+ bundle = new PropertyResourceBundle(new InputStreamReader(stream, StandardCharsets.UTF_8));
} finally {
stream.close();
}
diff --git a/src/main/java/org/orekit/estimation/iod/IodGibbs.java b/src/main/java/org/orekit/estimation/iod/IodGibbs.java
index 964c8d82929820ab95719f1531c833970474b85a..8a73bd26279a2cff7de2908206972838704ecd49 100644
--- a/src/main/java/org/orekit/estimation/iod/IodGibbs.java
+++ b/src/main/java/org/orekit/estimation/iod/IodGibbs.java
@@ -118,6 +118,24 @@ public class IodGibbs {
final AbsoluteDate date = date2;
// compute the equivalent Keplerian orbit
- return new KeplerianOrbit(pv, frame, date, mu);
+ final KeplerianOrbit orbit = new KeplerianOrbit(pv, frame, date, mu);
+
+ //define the reverse orbit
+ final PVCoordinates pv2 = new PVCoordinates(r2, vlEci.scalarMultiply(-1));
+ final KeplerianOrbit orbit2 = new KeplerianOrbit(pv2, frame, date, mu);
+
+ //check which orbit is correct
+ final Vector3D estP3 = orbit.shiftedBy(date3.durationFrom(date2)).
+ getPVCoordinates().getPosition();
+ final double dist = estP3.subtract(r3).getNorm();
+ final Vector3D estP3_2 = orbit2.shiftedBy(date3.durationFrom(date2)).
+ getPVCoordinates().getPosition();
+ final double dist2 = estP3_2.subtract(r3).getNorm();
+
+ if (dist <= dist2) {
+ return orbit;
+ } else {
+ return orbit2;
+ }
}
}
diff --git a/src/main/java/org/orekit/estimation/iod/IodGooding.java b/src/main/java/org/orekit/estimation/iod/IodGooding.java
index 325d5700024580e930397163a4bb2ccad8c6d2c8..23674096ec30a46e5bc4dac5612d0c3628d6d9a0 100644
--- a/src/main/java/org/orekit/estimation/iod/IodGooding.java
+++ b/src/main/java/org/orekit/estimation/iod/IodGooding.java
@@ -135,18 +135,21 @@ public class IodGooding {
* @param lineOfSight1 line of sight 1
* @param dateObs1 date of observation 1
* @param lineOfSight2 line of sight 2
- * @param dateObs2 date of observation 1
+ * @param dateObs2 date of observation 2
* @param lineOfSight3 line of sight 3
- * @param dateObs3 date of observation 1
+ * @param dateObs3 date of observation 3
* @param rho1init initial guess of the range problem. range 1, in meters
* @param rho3init initial guess of the range problem. range 3, in meters
+ * @param nRev number of complete revolutions between observation1 and 3
+ * @param direction true if posigrade (short way)
* @return an estimate of the Keplerian orbit
*/
public KeplerianOrbit estimate(final Vector3D O1, final Vector3D O2, final Vector3D O3,
final Vector3D lineOfSight1, final AbsoluteDate dateObs1,
final Vector3D lineOfSight2, final AbsoluteDate dateObs2,
final Vector3D lineOfSight3, final AbsoluteDate dateObs3,
- final double rho1init, final double rho3init) {
+ final double rho1init, final double rho3init, final int nRev,
+ final boolean direction) {
this.date1 = dateObs1;
@@ -167,8 +170,8 @@ public class IodGooding {
// solve the range problem
solveRangeProblem(rho1init / R, rho3init / R,
dateObs3.durationFrom(dateObs1) / T, dateObs2.durationFrom(dateObs1) / T,
- 0,
- true,
+ nRev,
+ direction,
lineOfSight1, lineOfSight2, lineOfSight3,
maxiter);
@@ -180,6 +183,32 @@ public class IodGooding {
return gibbs.estimate(frame, p1, dateObs1, p2, dateObs2, p3, dateObs3);
}
+ /** Orbit got from Observed Three Lines of Sight (angles only).
+ * assuming there was less than an half revolution between start and final date
+ *
+ * @param O1 Observer position 1
+ * @param O2 Observer position 2
+ * @param O3 Observer position 3
+ * @param lineOfSight1 line of sight 1
+ * @param dateObs1 date of observation 1
+ * @param lineOfSight2 line of sight 2
+ * @param dateObs2 date of observation 1
+ * @param lineOfSight3 line of sight 3
+ * @param dateObs3 date of observation 1
+ * @param rho1init initial guess of the range problem. range 1, in meters
+ * @param rho3init initial guess of the range problem. range 3, in meters
+ * @return an estimate of the Keplerian orbit
+ */
+ public KeplerianOrbit estimate(final Vector3D O1, final Vector3D O2, final Vector3D O3,
+ final Vector3D lineOfSight1, final AbsoluteDate dateObs1,
+ final Vector3D lineOfSight2, final AbsoluteDate dateObs2,
+ final Vector3D lineOfSight3, final AbsoluteDate dateObs3,
+ final double rho1init, final double rho3init) {
+
+ return this.estimate(O1, O2, O3, lineOfSight1, dateObs1, lineOfSight2, dateObs2,
+ lineOfSight3, dateObs3, rho1init, rho3init, 0, true);
+ }
+
/** Solve the range problem when three line of sight are given.
*
* @param rho1init initial value for range R1, in meters
@@ -248,14 +277,17 @@ public class IodGooding {
// They should be zero when line of sight 2 and current direction for 2 from O2 are aligned.
final Vector3D u = lineOfSight2.crossProduct(C);
final Vector3D P = (u.crossProduct(lineOfSight2)).normalize();
- final Vector3D EN = (lineOfSight2.crossProduct(P)).normalize();
+ final Vector3D ENt = lineOfSight2.crossProduct(P);
- // if EN is zero we have a solution!
- final double ENR = EN.getNorm();
+ // if ENt is zero we have a solution!
+ final double ENR = ENt.getNorm();
if (ENR == 0.) {
return true;
}
+ //Normalize EN
+ final Vector3D EN = ENt.normalize();
+
// Coordinate along 'F function'
final double Fc = P.dotProduct(C);
//Gc = EN.dotProduct(C);
@@ -385,8 +417,8 @@ public class IodGooding {
final double Gp1 = EN.dotProduct(Cp1);
// derivatives df/drho1 and dg/drho1
- final double Fx = (Fp1 - Fm1) / (2 * dx);
- final double Gx = (Gp1 - Gm1) / (2 * dx);
+ final double Fx = (Fp1 - Fm1) / (2. * dx);
+ final double Gx = (Gp1 - Gm1) / (2. * dx);
final Vector3D Cm3 = getPositionOnLoS2 (lineOfSight1, x,
lineOfSight3, y - dy,
@@ -408,10 +440,10 @@ public class IodGooding {
final double detJac = Fx * Gy - Fy * Gx;
// Coefficients for the classical Newton-Raphson iterative method
- FD[0] = Fx / detJac;
- FD[1] = Fy / detJac;
- GD[0] = Gx / detJac;
- GD[1] = Gy / detJac;
+ FD[0] = Fx;
+ FD[1] = Fy;
+ GD[0] = Gx;
+ GD[1] = Gy;
// Modified Newton-Raphson process, with Halley's method to have cubic convergence.
// This requires computing second order derivatives.
@@ -431,17 +463,17 @@ public class IodGooding {
T13, T12, nrev, direction).subtract(vObserverPosition2);
// f function value at (x1+dx1, x3+dx3)
- final double Fp13 = P.dotProduct(Cp13) - F;
+ final double Fp13 = P.dotProduct(Cp13);
// g function value at (x1+dx1, x3+dx3)
final double Gp13 = EN.dotProduct(Cp13);
- final Vector3D Cm13 = getPositionOnLoS2 (lineOfSight1, x + dx,
- lineOfSight3, y + dy,
+ final Vector3D Cm13 = getPositionOnLoS2 (lineOfSight1, x - dx,
+ lineOfSight3, y - dy,
T13, T12, nrev, direction).subtract(vObserverPosition2);
- // f function value at (x1+dx1, x3+dx3)
- final double Fm13 = P.dotProduct(Cm13) - F;
- // g function value at (x1+dx1, x3+dx3)
+ // f function value at (x1-dx1, x3-dx3)
+ final double Fm13 = P.dotProduct(Cm13);
+ // g function value at (x1-dx1, x3-dx3)
final double Gm13 = EN.dotProduct(Cm13);
// Second order derivatives: d^2f / drho1drho3 and d^2g / drho1drho3
@@ -449,8 +481,8 @@ public class IodGooding {
// 0.5 * (Fxx * dx / dy + Fyy * dy / dx);
//double Gxy = Gp13 / (dx * dy) - (Gx / dy + Gy / dx) -
// 0.5 * (Gxx * dx / dy + Gyy * dy / dx);
- final double Fxy = (Fp13 + Fm13) / (2 * dx * dy) - 1.0 * (Fxx / 2 + Fyy / 2) - F / (dx * dy);
- final double Gxy = (Gp13 + Gm13) / (2 * dx * dy) - 1.0 * (Gxx / 2 + Gyy / 2) - F / (dx * dy);
+ final double Fxy = (Fp13 + Fm13) / (2 * dx * dy) - 0.5 * (Fxx * dx / dy + Fyy * dy / dx) - F / (dx * dy);
+ final double Gxy = (Gp13 + Gm13) / (2 * dx * dy) - 0.5 * (Gxx * dx / dy + Gyy * dy / dx) - F / (dx * dy);
// delta Newton Raphson, 1st order step
final double dx3NR = -Gy * F / detJac;
@@ -464,7 +496,7 @@ public class IodGooding {
final double FxH = Fx + 0.5 * (Fxx * dx3NR + Fxy * dx1NR);
final double FyH = Fy + 0.5 * (Fxy * dx3NR + Fxx * dx1NR);
final double GxH = Gx + 0.5 * (Gxx * dx3NR + Gxy * dx1NR);
- final double GyH = Gy + 0.5 * (Gxy * dx3NR + Fxy * dx1NR);
+ final double GyH = Gy + 0.5 * (Gxy * dx3NR + Gyy * dx1NR);
// New Halley's method "Jacobian"
FD[0] = FxH;
@@ -489,7 +521,7 @@ public class IodGooding {
private Vector3D getPositionOnLoS2(final Vector3D E1, final double RO1,
final Vector3D E3, final double RO3,
final double T13, final double T12,
- final double nRev, final boolean posigrade) {
+ final int nRev, final boolean posigrade) {
final Vector3D P1 = vObserverPosition1.add(E1.scalarMultiply(RO1));
R1 = P1.getNorm();
@@ -504,14 +536,13 @@ public class IodGooding {
// compute the number of revolutions
if (!posigrade) {
- TH = FastMath.PI - TH;
+ TH = 2 * FastMath.PI - TH;
}
- TH = TH + nRev * FastMath.PI;
// Solve the Lambert's problem to get the velocities at endpoints
final double[] V1 = new double[2];
// work with non-dimensional units (MU=1)
- final boolean exitflag = lambert.solveLambertPb(R1, R3, TH, T13, 0, V1);
+ final boolean exitflag = lambert.solveLambertPb(R1, R3, TH, T13, nRev, V1);
if (exitflag) {
// basis vectors
diff --git a/src/main/java/org/orekit/estimation/iod/IodLambert.java b/src/main/java/org/orekit/estimation/iod/IodLambert.java
index 5be53e67a1e3cf96ee00a7d66d995e0fac29be71..5f081196ffb02156847a16a0c085f6d40e128927 100644
--- a/src/main/java/org/orekit/estimation/iod/IodLambert.java
+++ b/src/main/java/org/orekit/estimation/iod/IodLambert.java
@@ -18,6 +18,8 @@ package org.orekit.estimation.iod;
import org.hipparchus.geometry.euclidean.threed.Vector3D;
import org.hipparchus.util.FastMath;
+import org.orekit.errors.OrekitException;
+import org.orekit.errors.OrekitMessages;
import org.orekit.frames.Frame;
import org.orekit.orbits.KeplerianOrbit;
import org.orekit.time.AbsoluteDate;
@@ -51,7 +53,7 @@ public class IodLambert {
*
* The logic for setting {@code posigrade} and {@code nRev} is that the
* sweep angle Δυ travelled by the object between {@code t1} and {@code t2} is
- * 2π {@code nRev} - α if {@code posigrade} is false and 2π {@code nRev} + α
+ * 2π {@code nRev +1} - α if {@code posigrade} is false and 2π {@code nRev} + α
* if {@code posigrade} is true, where α is the separation angle between
* {@code p1} and {@code p2}, which is always computed between 0 and π
* (because in 3D without a normal reference, vector angles cannot go past π).
@@ -68,7 +70,7 @@ public class IodLambert {
* then {@code posigrade} should be {@code true} and {@code nRev} should be 0.
* If {@code t2} is more than half a period after {@code t1} but less than
* one period after {@code t1}, {@code posigrade} should be {@code false} and
- * {@code nRev} should be 1.
+ * {@code nRev} should be 0.
*
* @param frame frame
* @param posigrade flag indicating the direction of motion
@@ -88,6 +90,11 @@ public class IodLambert {
final double r2 = p2.getNorm();
final double tau = t2.durationFrom(t1); // in seconds
+ // Exception if t2 < t1
+ if (tau < 0.0) {
+ throw new OrekitException(OrekitMessages.NON_CHRONOLOGICAL_DATES_FOR_OBSERVATIONS, t1, t2);
+ }
+
// normalizing constants
final double R = FastMath.max(r1, r2); // in m
final double V = FastMath.sqrt(mu / R); // in m/s
@@ -99,7 +106,6 @@ public class IodLambert {
if (!posigrade) {
dth = 2 * FastMath.PI - dth;
}
- dth = dth + nRev * 2 * FastMath.PI;
// velocity vectors in the orbital plane, in the R-T frame
final double[] Vdep = new double[2];
@@ -146,18 +152,15 @@ public class IodLambert {
final int mRev, final double[] V1) {
// decide whether to use the left or right branch (for multi-revolution
// problems), and the long- or short way.
- final boolean leftbranch = FastMath.signum(mRev) > 0;
- int longway = 0;
- if (tau > 0) {
- longway = 1;
+ final boolean leftbranch = dth < FastMath.PI;
+ int longway = 1;
+ if (dth > FastMath.PI) {
+ longway = -1;
}
final int m = FastMath.abs(mRev);
final double rtof = FastMath.abs(tau);
- double theta = dth;
- if (longway < 0) {
- theta = 2 * FastMath.PI - dth;
- }
+ final double theta = dth;
// non-dimensional chord ||r2-r1||
final double chord = FastMath.sqrt(r1 * r1 + r2 * r2 - 2 * r1 * r2 * FastMath.cos(theta));
@@ -169,7 +172,7 @@ public class IodLambert {
final double minSma = speri / 2.;
// lambda parameter (Eq 7.6)
- final double lambda = FastMath.sqrt(1 - chord / speri);
+ final double lambda = longway * FastMath.sqrt(1 - chord / speri);
// reference tof value for the Newton solver
final double logt = FastMath.log(rtof);
diff --git a/src/main/java/org/orekit/estimation/iod/IodLaplace.java b/src/main/java/org/orekit/estimation/iod/IodLaplace.java
new file mode 100644
index 0000000000000000000000000000000000000000..ee5ea3a5afa4399156abaa19ad37b9778a1c755b
--- /dev/null
+++ b/src/main/java/org/orekit/estimation/iod/IodLaplace.java
@@ -0,0 +1,157 @@
+/* Copyright 2002-2019 CS Systèmes d'Information
+ * Licensed to CS Systèmes d'Information (CS) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.estimation.iod;
+
+import org.hipparchus.analysis.solvers.LaguerreSolver;
+import org.hipparchus.complex.Complex;
+import org.hipparchus.geometry.euclidean.threed.Vector3D;
+import org.hipparchus.linear.Array2DRowRealMatrix;
+import org.hipparchus.linear.LUDecomposition;
+import org.hipparchus.util.FastMath;
+import org.orekit.frames.Frame;
+import org.orekit.orbits.CartesianOrbit;
+import org.orekit.time.AbsoluteDate;
+import org.orekit.utils.PVCoordinates;
+
+/**
+ * Laplace angles-only initial orbit determination, assuming Keplerian motion.
+ * An orbit is determined from three angular observations from the same site.
+ *
+ *
+ * Reference:
+ * Bate, R., Mueller, D. D., & White, J. E. (1971). Fundamentals of astrodynamics.
+ * New York: Dover Publications.
+ *
+ * @author Shiva Iyer
+ * @since 10.1
+ */
+public class IodLaplace {
+
+ /** Gravitational constant. */
+ private final double mu;
+
+ /** Constructor.
+ *
+ * @param mu gravitational constant
+ */
+ public IodLaplace(final double mu) {
+ this.mu = mu;
+ }
+
+ /** Estimate orbit from three line of sight angles from the same location.
+ *
+ * @param frame Intertial frame for observer coordinates and orbit estimate
+ * @param obsPva Observer coordinates at time obsDate2
+ * @param obsDate1 date of observation 1
+ * @param los1 line of sight unit vector 1
+ * @param obsDate2 date of observation 2
+ * @param los2 line of sight unit vector 2
+ * @param obsDate3 date of observation 3
+ * @param los3 line of sight unit vector 3
+ * @return estimate of the orbit at the central date dateObs2 or null if
+ * no estimate is possible with the given data
+ */
+ public CartesianOrbit estimate(final Frame frame, final PVCoordinates obsPva,
+ final AbsoluteDate obsDate1, final Vector3D los1,
+ final AbsoluteDate obsDate2, final Vector3D los2,
+ final AbsoluteDate obsDate3, final Vector3D los3) {
+ // The first observation is taken as t1 = 0
+ final double t2 = obsDate2.durationFrom(obsDate1);
+ final double t3 = obsDate3.durationFrom(obsDate1);
+
+ // Calculate the first and second derivatives of the Line Of Sight vector at t2
+ final Vector3D Ldot = los1.scalarMultiply((t2 - t3) / (t2 * t3)).
+ add(los2.scalarMultiply((2.0 * t2 - t3) / (t2 * (t2 - t3)))).
+ add(los3.scalarMultiply(t2 / (t3 * (t3 - t2))));
+ final Vector3D Ldotdot = los1.scalarMultiply(2.0 / (t2 * t3)).
+ add(los2.scalarMultiply(2.0 / (t2 * (t2 - t3)))).
+ add(los3.scalarMultiply(2.0 / (t3 * (t3 - t2))));
+
+ // The determinant will vanish if the observer lies in the plane of the orbit at t2
+ final double D = 2.0 * getDeterminant(los2, Ldot, Ldotdot);
+ if (FastMath.abs(D) < 1.0E-14) {
+ return null;
+ }
+
+ final double Dsq = D * D;
+ final double R = obsPva.getPosition().getNorm();
+ final double RdotL = obsPva.getPosition().dotProduct(los2);
+
+ final double D1 = getDeterminant(los2, Ldot, obsPva.getAcceleration());
+ final double D2 = getDeterminant(los2, Ldot, obsPva.getPosition());
+
+ // Coefficients of the 8th order polynomial we need to solve to determine "r"
+ final double[] coeff = new double[] {-4.0 * mu * mu * D2 * D2 / Dsq,
+ 0.0,
+ 0.0,
+ 4.0 * mu * D2 * (RdotL / D - 2.0 * D1 / Dsq),
+ 0.0,
+ 0.0,
+ 4.0 * D1 * RdotL / D - 4.0 * D1 * D1 / Dsq - R * R, 0.0,
+ 1.0};
+
+ // Use the Laguerre polynomial solver and take the initial guess to be
+ // 5 times the observer's position magnitude
+ final LaguerreSolver solver = new LaguerreSolver(1E-10, 1E-10, 1E-10);
+ final Complex[] roots = solver.solveAllComplex(coeff, 5.0 * R);
+
+ // We consider "r" to be the positive real root with the largest magnitude
+ double rMag = 0.0;
+ for (int i = 0; i < roots.length; i++) {
+ if (roots[i].getReal() > rMag &&
+ FastMath.abs(roots[i].getImaginary()) < solver.getAbsoluteAccuracy()) {
+ rMag = roots[i].getReal();
+ }
+ }
+ if (rMag == 0.0) {
+ return null;
+ }
+
+ // Calculate rho, the slant range from the observer to the satellite at t2.
+ // This yields the "r" vector, which is the satellite's position vector at t2.
+ final double rCubed = rMag * rMag * rMag;
+ final double rho = -2.0 * D1 / D - 2.0 * mu * D2 / (D * rCubed);
+ final Vector3D posVec = los2.scalarMultiply(rho).add(obsPva.getPosition());
+
+ // Calculate rho_dot at t2, which will yield the satellite's velocity vector at t2
+ final double D3 = getDeterminant(los2, obsPva.getAcceleration(), Ldotdot);
+ final double D4 = getDeterminant(los2, obsPva.getPosition(), Ldotdot);
+ final double rhoDot = -D3 / D - mu * D4 / (D * rCubed);
+ final Vector3D velVec = los2.scalarMultiply(rhoDot).
+ add(Ldot.scalarMultiply(rho)).
+ add(obsPva.getVelocity());
+
+ // Return the estimated orbit
+ return new CartesianOrbit(new PVCoordinates(posVec, velVec), frame, obsDate2, mu);
+ }
+
+ /** Calculate the determinant of the matrix with given column vectors.
+ *
+ * @param col0 Matrix column 0
+ * @param col1 Matrix column 1
+ * @param col2 Matrix column 2
+ * @return matrix determinant
+ *
+ */
+ private double getDeterminant(final Vector3D col0, final Vector3D col1, final Vector3D col2) {
+ final Array2DRowRealMatrix mat = new Array2DRowRealMatrix(3, 3);
+ mat.setColumn(0, col0.toArray());
+ mat.setColumn(1, col1.toArray());
+ mat.setColumn(2, col2.toArray());
+ return new LUDecomposition(mat).getDeterminant();
+ }
+}
diff --git a/src/main/java/org/orekit/estimation/leastsquares/BatchLSEstimator.java b/src/main/java/org/orekit/estimation/leastsquares/BatchLSEstimator.java
index f13e2eb2c5d7326c65d34a99b70f3d10db13432a..50d55680a90a7ecb56b0811567f06e4e58a1acda 100644
--- a/src/main/java/org/orekit/estimation/leastsquares/BatchLSEstimator.java
+++ b/src/main/java/org/orekit/estimation/leastsquares/BatchLSEstimator.java
@@ -72,8 +72,8 @@ public class BatchLSEstimator {
/** Solver for least squares problem. */
private final LeastSquaresOptimizer optimizer;
- /** Convergence threshold on normalized parameters. */
- private double parametersConvergenceThreshold;
+ /** Convergence checker. */
+ private ConvergenceChecker convergenceChecker;
/** Builder for the least squares problem. */
private final LeastSquaresBuilder lsBuilder;
@@ -121,12 +121,13 @@ public class BatchLSEstimator {
this.builders = propagatorBuilder;
this.measurements = new ArrayList>();
this.optimizer = optimizer;
- this.parametersConvergenceThreshold = Double.NaN;
this.lsBuilder = new LeastSquaresBuilder();
this.observer = null;
this.estimations = null;
this.orbits = new Orbit[builders.length];
+ setParametersConvergenceThreshold(Double.NaN);
+
// our model computes value and Jacobian in one call,
// so we don't use the lazy evaluation feature
lsBuilder.lazyEvaluation(false);
@@ -286,13 +287,35 @@ public class BatchLSEstimator {
* the scale is often small (typically about 1 m for orbital positions
* for example), then the threshold should not be too small. A value
* of 10⁻³ is often quite accurate.
+ *
+ *
+ * Calling this method overrides any checker that could have been set
+ * beforehand by calling {@link #setConvergenceChecker(ConvergenceChecker)}.
+ * Both methods are mutually exclusive.
+ *
*
* @param parametersConvergenceThreshold convergence threshold on
* normalized parameters (dimensionless, related to parameters scales)
+ * @see #setConvergenceChecker(ConvergenceChecker)
* @see EvaluationRmsChecker
*/
public void setParametersConvergenceThreshold(final double parametersConvergenceThreshold) {
- this.parametersConvergenceThreshold = parametersConvergenceThreshold;
+ setConvergenceChecker((iteration, previous, current) ->
+ current.getPoint().getLInfDistance(previous.getPoint()) <= parametersConvergenceThreshold);
+ }
+
+ /** Set a custom convergence checker.
+ *
+ * Calling this method overrides any checker that could have been set
+ * beforehand by calling {@link #setParametersConvergenceThreshold(double)}.
+ * Both methods are mutually exclusive.
+ *
+ * @param convergenceChecker convergence checker to set
+ * @see #setParametersConvergenceThreshold(double)
+ * @since 10.1
+ */
+ public void setConvergenceChecker(final ConvergenceChecker convergenceChecker) {
+ this.convergenceChecker = convergenceChecker;
}
/** Estimate the orbital, propagation and measurements parameters.
@@ -398,16 +421,7 @@ public class BatchLSEstimator {
estimatedPropagatorParameters,
estimatedMeasurementsParameters));
- lsBuilder.checker(new ConvergenceChecker() {
- /** {@inheritDoc} */
- @Override
- public boolean converged(final int iteration,
- final LeastSquaresProblem.Evaluation previous,
- final LeastSquaresProblem.Evaluation current) {
- final double lInf = current.getPoint().getLInfDistance(previous.getPoint());
- return lInf <= parametersConvergenceThreshold;
- }
- });
+ lsBuilder.checker(convergenceChecker);
// set up the problem to solve
final LeastSquaresProblem problem = new TappedLSProblem(lsBuilder.build(),
diff --git a/src/main/java/org/orekit/estimation/leastsquares/MeasurementHandler.java b/src/main/java/org/orekit/estimation/leastsquares/MeasurementHandler.java
index bd911179bb276a460b0204e327334a1de3c7d217..0958e526230828f4bef4ef06c8cfb04745bf3c4e 100644
--- a/src/main/java/org/orekit/estimation/leastsquares/MeasurementHandler.java
+++ b/src/main/java/org/orekit/estimation/leastsquares/MeasurementHandler.java
@@ -20,6 +20,7 @@ import java.util.List;
import org.orekit.errors.OrekitInternalError;
import org.orekit.estimation.measurements.EstimatedMeasurement;
+import org.orekit.estimation.measurements.ObservableSatellite;
import org.orekit.estimation.measurements.ObservedMeasurement;
import org.orekit.propagation.SpacecraftState;
import org.orekit.propagation.sampling.MultiSatStepHandler;
@@ -90,7 +91,8 @@ class MeasurementHandler implements MultiSatStepHandler {
// estimate the theoretical measurement
final SpacecraftState[] states = new SpacecraftState[observed.getSatellites().size()];
for (int i = 0; i < states.length; ++i) {
- states[i] = interpolators.get(i).getInterpolatedState(next.getDate());
+ final ObservableSatellite satellite = observed.getSatellites().get(i);
+ states[i] = interpolators.get(satellite.getPropagatorIndex()).getInterpolatedState(next.getDate());
}
final EstimatedMeasurement> estimated = observed.estimate(model.getIterationsCount(),
model.getEvaluationsCount(),
diff --git a/src/main/java/org/orekit/estimation/measurements/AngularAzEl.java b/src/main/java/org/orekit/estimation/measurements/AngularAzEl.java
index f6bbcca186fbe6b33b6c10873628cceb056a882a..4392e8a59ea2878326b261d47ff56d6ed632eee1 100644
--- a/src/main/java/org/orekit/estimation/measurements/AngularAzEl.java
+++ b/src/main/java/org/orekit/estimation/measurements/AngularAzEl.java
@@ -84,8 +84,7 @@ public class AngularAzEl extends AbstractMeasurement {
protected EstimatedMeasurement theoreticalEvaluation(final int iteration, final int evaluation,
final SpacecraftState[] states) {
- final ObservableSatellite satellite = getSatellites().get(0);
- final SpacecraftState state = states[satellite.getPropagatorIndex()];
+ final SpacecraftState state = states[0];
// Azimuth/elevation derivatives are computed with respect to spacecraft state in inertial frame
// and station parameters
diff --git a/src/main/java/org/orekit/estimation/measurements/AngularRaDec.java b/src/main/java/org/orekit/estimation/measurements/AngularRaDec.java
index a11c6019ff490d45d34fd40097a9ee0323313b7c..44d1695e9387ec3dae50bee37186b88760357f6b 100644
--- a/src/main/java/org/orekit/estimation/measurements/AngularRaDec.java
+++ b/src/main/java/org/orekit/estimation/measurements/AngularRaDec.java
@@ -99,8 +99,7 @@ public class AngularRaDec extends AbstractMeasurement {
protected EstimatedMeasurement theoreticalEvaluation(final int iteration, final int evaluation,
final SpacecraftState[] states) {
- final ObservableSatellite satellite = getSatellites().get(0);
- final SpacecraftState state = states[satellite.getPropagatorIndex()];
+ final SpacecraftState state = states[0];
// Right Ascension/elevation (in reference frame )derivatives are computed with respect to spacecraft state in inertial frame
// and station parameters
diff --git a/src/main/java/org/orekit/estimation/measurements/EstimatedMeasurement.java b/src/main/java/org/orekit/estimation/measurements/EstimatedMeasurement.java
index 09b663a362aaebba05f27a8fb7d384afc9913436..dec91692e2c7f22bc32a128183e2066f58444328 100644
--- a/src/main/java/org/orekit/estimation/measurements/EstimatedMeasurement.java
+++ b/src/main/java/org/orekit/estimation/measurements/EstimatedMeasurement.java
@@ -179,6 +179,18 @@ public class EstimatedMeasurement> implements C
this.status = status;
}
+ /** Get state size.
+ *
+ * Warning, the {@link #setStateDerivatives(int, double[][])}
+ * method must have been called before this method is called.
+ *
+ * @return state size
+ * @since 10.1
+ */
+ public int getStateSize() {
+ return stateDerivatives[0][0].length;
+ }
+
/** Get the partial derivatives of the {@link #getEstimatedValue()
* simulated measurement} with respect to state Cartesian coordinates.
* @param index index of the state, according to the {@code states}
diff --git a/src/main/java/org/orekit/estimation/measurements/InterSatellitesRange.java b/src/main/java/org/orekit/estimation/measurements/InterSatellitesRange.java
index 83aeb1124a6491df46d06669b1cbd4155e5060a1..80366e6c50a6b805dfd2128128b4991b967ad14e 100644
--- a/src/main/java/org/orekit/estimation/measurements/InterSatellitesRange.java
+++ b/src/main/java/org/orekit/estimation/measurements/InterSatellitesRange.java
@@ -124,19 +124,17 @@ public class InterSatellitesRange extends AbstractMeasurement pvaL = getCoordinates(stateL, 0, factory);
- final ObservableSatellite remote = getSatellites().get(1);
- final SpacecraftState stateR = states[remote.getPropagatorIndex()];
- final TimeStampedFieldPVCoordinates pvaR = getCoordinates(stateR, 6, factory);
+ final SpacecraftState local = states[0];
+ final TimeStampedFieldPVCoordinates pvaL = getCoordinates(local, 0, factory);
+ final SpacecraftState remote = states[1];
+ final TimeStampedFieldPVCoordinates pvaR = getCoordinates(remote, 6, factory);
// compute propagation times
// (if state has already been set up to pre-compensate propagation delay,
// we will have delta == tauD and transitState will be the same as state)
// downlink delay
- final DerivativeStructure dtl = local.getClockOffsetDriver().getValue(factory, indices);
+ final DerivativeStructure dtl = getSatellites().get(0).getClockOffsetDriver().getValue(factory, indices);
final FieldAbsoluteDate arrivalDate =
new FieldAbsoluteDate(getDate(), dtl.negate());
@@ -145,7 +143,7 @@ public class InterSatellitesRange extends AbstractMeasurement(this, iteration, evaluation,
new SpacecraftState[] {
- stateL.shiftedBy(deltaMTauD.getValue()),
- stateR.shiftedBy(deltaMTauD.getValue())
+ local.shiftedBy(deltaMTauD.getValue()),
+ remote.shiftedBy(deltaMTauD.getValue())
}, new TimeStampedPVCoordinates[] {
- stateL.shiftedBy(delta - tauD.getValue() - tauU.getValue()).getPVCoordinates(),
- stateR.shiftedBy(delta - tauD.getValue()).getPVCoordinates(),
- stateL.shiftedBy(delta).getPVCoordinates()
+ local.shiftedBy(delta - tauD.getValue() - tauU.getValue()).getPVCoordinates(),
+ remote.shiftedBy(delta - tauD.getValue()).getPVCoordinates(),
+ local.shiftedBy(delta).getPVCoordinates()
});
// Range value
@@ -177,15 +175,15 @@ public class InterSatellitesRange extends AbstractMeasurement(this, iteration, evaluation,
new SpacecraftState[] {
- stateL.shiftedBy(deltaMTauD.getValue()),
- stateR.shiftedBy(deltaMTauD.getValue())
+ local.shiftedBy(deltaMTauD.getValue()),
+ remote.shiftedBy(deltaMTauD.getValue())
}, new TimeStampedPVCoordinates[] {
- stateR.shiftedBy(delta - tauD.getValue()).getPVCoordinates(),
- stateL.shiftedBy(delta).getPVCoordinates()
+ remote.shiftedBy(delta - tauD.getValue()).getPVCoordinates(),
+ local.shiftedBy(delta).getPVCoordinates()
});
// Clock offsets
- final DerivativeStructure dtr = remote.getClockOffsetDriver().getValue(factory, indices);
+ final DerivativeStructure dtr = getSatellites().get(1).getClockOffsetDriver().getValue(factory, indices);
// Range value
range = tauD.add(dtl).subtract(dtr).multiply(Constants.SPEED_OF_LIGHT);
diff --git a/src/main/java/org/orekit/estimation/measurements/MultiplexedMeasurement.java b/src/main/java/org/orekit/estimation/measurements/MultiplexedMeasurement.java
new file mode 100644
index 0000000000000000000000000000000000000000..ae097af5b944de0021bedf80bff95e8380df1f6f
--- /dev/null
+++ b/src/main/java/org/orekit/estimation/measurements/MultiplexedMeasurement.java
@@ -0,0 +1,277 @@
+/* Copyright 2002-2019 CS Systèmes d'Information
+ * Licensed to CS Systèmes d'Information (CS) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.estimation.measurements;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+
+import org.orekit.propagation.SpacecraftState;
+import org.orekit.utils.ParameterDriver;
+import org.orekit.utils.ParameterDriversList;
+import org.orekit.utils.TimeStampedPVCoordinates;
+
+/** Class multiplexing several measurements as one.
+ *
+ * Date comes from the first measurement, observed and estimated
+ * values result from gathering all underlying measurements values.
+ *
+ * @author Luc Maisonobe
+ * @since 10.1
+ */
+public class MultiplexedMeasurement extends AbstractMeasurement {
+
+ /** Multiplexed measurements. */
+ private final List> observedMeasurements;
+
+ /** Multiplexed measurements. */
+ private final List> estimatedMeasurements;
+
+ /** Multiplexed parameters drivers. */
+ private ParameterDriversList parametersDrivers;
+
+ /** Total dimension. */
+ private final int dimension;
+
+ /** Total number of satellites involved. */
+ private final int nbSat;
+
+ /** States mapping. */
+ private final int[][] mapping;
+
+ /** Simple constructor.
+ * @param measurements measurements to multiplex
+ * @since 10.1
+ */
+ public MultiplexedMeasurement(final List> measurements) {
+ super(measurements.get(0).getDate(),
+ multiplex(measurements, m -> m.getObservedValue()),
+ multiplex(measurements, m -> m.getTheoreticalStandardDeviation()),
+ multiplex(measurements, m -> m.getBaseWeight()),
+ multiplex(measurements));
+
+ this.observedMeasurements = measurements;
+ this.estimatedMeasurements = new ArrayList<>();
+ this.parametersDrivers = new ParameterDriversList();
+
+ // gather parameters drivers
+ int dim = 0;
+ for (final ObservedMeasurement> m : measurements) {
+ for (final ParameterDriver driver : m.getParametersDrivers()) {
+ parametersDrivers.add(driver);
+ }
+ dim += m.getDimension();
+ }
+ parametersDrivers.sort();
+ for (final ParameterDriver driver : parametersDrivers.getDrivers()) {
+ addParameterDriver(driver);
+ }
+ this.dimension = dim;
+
+ // set up states mappings for observed satellites
+ final List deduplicated = getSatellites();
+ this.nbSat = deduplicated.size();
+ this.mapping = new int[measurements.size()][];
+ for (int i = 0; i < mapping.length; ++i) {
+ final List satellites = measurements.get(i).getSatellites();
+ mapping[i] = new int[satellites.size()];
+ for (int j = 0; j < mapping[i].length; ++j) {
+ final int index = satellites.get(j).getPropagatorIndex();
+ for (int k = 0; k < nbSat; ++k) {
+ if (deduplicated.get(k).getPropagatorIndex() == index) {
+ mapping[i][j] = k;
+ break;
+ }
+ }
+ }
+ }
+
+ }
+
+ /** Get the underlying measurements.
+ * @return underlying measurements
+ */
+ public List> getMeasurements() {
+ return observedMeasurements;
+ }
+
+ /** Get the underlying estimated measurements.
+ * @return underlying estimated measurements
+ */
+ public List> getEstimatedMeasurements() {
+ return estimatedMeasurements;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected EstimatedMeasurement theoreticalEvaluation(final int iteration, final int evaluation,
+ final SpacecraftState[] states) {
+
+ final SpacecraftState[] evaluationStates = new SpacecraftState[nbSat];
+ final List participants = new ArrayList<>();
+ final double[] value = new double[dimension];
+
+ // loop over all multiplexed measurements
+ estimatedMeasurements.clear();
+ int index = 0;
+ for (int i = 0; i < observedMeasurements.size(); ++i) {
+
+ // filter states involved in the current measurement
+ final SpacecraftState[] filteredStates = new SpacecraftState[mapping[i].length];
+ for (int j = 0; j < mapping[i].length; ++j) {
+ filteredStates[j] = states[mapping[i][j]];
+ }
+
+ // perform evaluation
+ final EstimatedMeasurement> eI = observedMeasurements.get(i).estimate(iteration, evaluation, filteredStates);
+ estimatedMeasurements.add(eI);
+
+ // extract results
+ final double[] valueI = eI.getEstimatedValue();
+ System.arraycopy(valueI, 0, value, index, valueI.length);
+ index += valueI.length;
+
+ // extract states
+ final SpacecraftState[] statesI = eI.getStates();
+ for (int j = 0; j < mapping[i].length; ++j) {
+ evaluationStates[mapping[i][j]] = statesI[j];
+ }
+
+ }
+
+ // create multiplexed estimation
+ final EstimatedMeasurement multiplexed =
+ new EstimatedMeasurement<>(this, iteration, evaluation,
+ evaluationStates,
+ participants.toArray(new TimeStampedPVCoordinates[0]));
+
+ // copy multiplexed value
+ multiplexed.setEstimatedValue(value);
+
+ // combine derivatives
+ final int stateSize = estimatedMeasurements.get(0).getStateSize();
+ final double[] zeroDerivative = new double[stateSize];
+ final double[][][] stateDerivatives = new double[nbSat][dimension][];
+ for (final double[][] m : stateDerivatives) {
+ Arrays.fill(m, zeroDerivative);
+ }
+
+ final Map parametersDerivatives = new IdentityHashMap<>();
+ index = 0;
+ for (int i = 0; i < observedMeasurements.size(); ++i) {
+
+ final EstimatedMeasurement> eI = estimatedMeasurements.get(i);
+ final int idx = index;
+ final int dimI = eI.getObservedMeasurement().getDimension();
+
+ // state derivatives
+ for (int j = 0; j < mapping[i].length; ++j) {
+ System.arraycopy(eI.getStateDerivatives(j), 0,
+ stateDerivatives[mapping[i][j]], index,
+ dimI);
+ }
+
+ // parameters derivatives
+ eI.getDerivativesDrivers().forEach(driver -> {
+ final ParameterDriversList.DelegatingDriver delegating = parametersDrivers.findByName(driver.getName());
+ double[] derivatives = parametersDerivatives.get(delegating);
+ if (derivatives == null) {
+ derivatives = new double[dimension];
+ parametersDerivatives.put(delegating, derivatives);
+ }
+ System.arraycopy(eI.getParameterDerivatives(driver), 0, derivatives, idx, dimI);
+ });
+
+ index += dimI;
+
+ }
+
+ // set states derivatives
+ for (int i = 0; i < nbSat; ++i) {
+ multiplexed.setStateDerivatives(i, stateDerivatives[i]);
+ }
+
+ // set parameters derivatives
+ parametersDerivatives.
+ entrySet().
+ stream().
+ forEach(e -> multiplexed.setParameterDerivatives(e.getKey(), e.getValue()));
+
+ return multiplexed;
+
+ }
+
+ /** Multiplex measurements data.
+ * @param measurements measurements to multiplex
+ * @param extractor data extraction function
+ * @return multiplexed data
+ */
+ private static double[] multiplex(final List> measurements,
+ final Function, double[]> extractor) {
+
+ // gather individual parts
+ final List parts = new ArrayList<> (measurements.size());
+ int n = 0;
+ for (final ObservedMeasurement> measurement : measurements) {
+ final double[] p = extractor.apply(measurement);
+ parts.add(p);
+ n += p.length;
+ }
+
+ // create multiplexed data
+ final double[] multiplexed = new double[n];
+ int index = 0;
+ for (final double[] p : parts) {
+ System.arraycopy(p, 0, multiplexed, index, p.length);
+ index += p.length;
+ }
+
+ return multiplexed;
+
+ }
+
+ /** Multiplex satellites data.
+ * @param measurements measurements to multiplex
+ * @return multiplexed satellites data
+ */
+ private static List multiplex(final List> measurements) {
+
+ final List satellites = new ArrayList<>();
+
+ // gather all satellites, removing duplicates
+ for (final ObservedMeasurement> measurement : measurements) {
+ for (final ObservableSatellite satellite : measurement.getSatellites()) {
+ boolean searching = true;
+ for (int i = 0; i < satellites.size() && searching; ++i) {
+ // check if we already know this satellite
+ searching = satellite.getPropagatorIndex() != satellites.get(i).getPropagatorIndex();
+ }
+ if (searching) {
+ // this is a new satellite, add it to the global list
+ satellites.add(satellite);
+ }
+ }
+ }
+
+ return satellites;
+
+ }
+
+}
diff --git a/src/main/java/org/orekit/estimation/measurements/ObservedMeasurement.java b/src/main/java/org/orekit/estimation/measurements/ObservedMeasurement.java
index 56107969a528891d3f2d9cf92ee5326fe11eba04..618e176c4f315ee622e7913d89875031a4bbf42d 100644
--- a/src/main/java/org/orekit/estimation/measurements/ObservedMeasurement.java
+++ b/src/main/java/org/orekit/estimation/measurements/ObservedMeasurement.java
@@ -131,7 +131,7 @@ public interface ObservedMeasurement> extends C
*
* @param iteration iteration number
* @param evaluation evaluations number
- * @param states orbital states at measurement date
+ * @param states orbital states corresponding to {@link #getSatellites()} at measurement date
* @return estimated measurement
*/
EstimatedMeasurement estimate(int iteration, int evaluation, SpacecraftState[] states);
diff --git a/src/main/java/org/orekit/estimation/measurements/PV.java b/src/main/java/org/orekit/estimation/measurements/PV.java
index 0440bd16a4a1bbe1013266c1206e3114d7bfdf06..733158e8060072831644870f6460527d09840eb1 100644
--- a/src/main/java/org/orekit/estimation/measurements/PV.java
+++ b/src/main/java/org/orekit/estimation/measurements/PV.java
@@ -226,9 +226,7 @@ public class PV extends AbstractMeasurement {
final SpacecraftState[] states) {
// PV value
- final ObservableSatellite satellite = getSatellites().get(0);
- final SpacecraftState state = states[satellite.getPropagatorIndex()];
- final TimeStampedPVCoordinates pv = state.getPVCoordinates();
+ final TimeStampedPVCoordinates pv = states[0].getPVCoordinates();
// prepare the evaluation
final EstimatedMeasurement estimated =
diff --git a/src/main/java/org/orekit/estimation/measurements/Position.java b/src/main/java/org/orekit/estimation/measurements/Position.java
index 624f82a8b76e00f3bdd55e96c7cb29431058d5e8..f0b589a463482a4030ac049d5b72c45b0ba98d7d 100644
--- a/src/main/java/org/orekit/estimation/measurements/Position.java
+++ b/src/main/java/org/orekit/estimation/measurements/Position.java
@@ -159,9 +159,7 @@ public class Position extends AbstractMeasurement {
final SpacecraftState[] states) {
// PV value
- final ObservableSatellite satellite = getSatellites().get(0);
- final SpacecraftState state = states[satellite.getPropagatorIndex()];
- final TimeStampedPVCoordinates pv = state.getPVCoordinates();
+ final TimeStampedPVCoordinates pv = states[0].getPVCoordinates();
// prepare the evaluation
final EstimatedMeasurement estimated =
diff --git a/src/main/java/org/orekit/estimation/measurements/Range.java b/src/main/java/org/orekit/estimation/measurements/Range.java
index de8819c7c3bcde7e53ef606d10bf0da05f1abfec..09523bac350dc770fbf78fb93a52b5ef058d5e30 100644
--- a/src/main/java/org/orekit/estimation/measurements/Range.java
+++ b/src/main/java/org/orekit/estimation/measurements/Range.java
@@ -135,8 +135,7 @@ public class Range extends AbstractMeasurement {
final int evaluation,
final SpacecraftState[] states) {
- final ObservableSatellite satellite = getSatellites().get(0);
- final SpacecraftState state = states[satellite.getPropagatorIndex()];
+ final SpacecraftState state = states[0];
// Range derivatives are computed with respect to spacecraft state in inertial frame
// and station parameters
@@ -225,8 +224,9 @@ public class Range extends AbstractMeasurement {
});
// Clock offsets
- final DerivativeStructure dtg = station.getClockOffsetDriver().getValue(factory, indices);
- final DerivativeStructure dts = satellite.getClockOffsetDriver().getValue(factory, indices);
+ final ObservableSatellite satellite = getSatellites().get(0);
+ final DerivativeStructure dts = satellite.getClockOffsetDriver().getValue(factory, indices);
+ final DerivativeStructure dtg = station.getClockOffsetDriver().getValue(factory, indices);
// Range value
range = tauD.add(dtg).subtract(dts).multiply(Constants.SPEED_OF_LIGHT);
diff --git a/src/main/java/org/orekit/estimation/measurements/RangeRate.java b/src/main/java/org/orekit/estimation/measurements/RangeRate.java
index 58da995067c5ebcc31d34a48b1eeb4bf836e4ca4..5ccc27d44c6dd45bd58a75bdb016e5c0a724fd60 100644
--- a/src/main/java/org/orekit/estimation/measurements/RangeRate.java
+++ b/src/main/java/org/orekit/estimation/measurements/RangeRate.java
@@ -101,8 +101,7 @@ public class RangeRate extends AbstractMeasurement {
protected EstimatedMeasurement theoreticalEvaluation(final int iteration, final int evaluation,
final SpacecraftState[] states) {
- final ObservableSatellite satellite = getSatellites().get(0);
- final SpacecraftState state = states[satellite.getPropagatorIndex()];
+ final SpacecraftState state = states[0];
// Range-rate derivatives are computed with respect to spacecraft state in inertial frame
// and station position in station's offset frame
diff --git a/src/main/java/org/orekit/estimation/measurements/TurnAroundRange.java b/src/main/java/org/orekit/estimation/measurements/TurnAroundRange.java
index d84e41cd8206a2f9615cfca906eaa70a8ccf8902..137987a41fb69b1759ca9951015470b03ce4e767 100644
--- a/src/main/java/org/orekit/estimation/measurements/TurnAroundRange.java
+++ b/src/main/java/org/orekit/estimation/measurements/TurnAroundRange.java
@@ -122,8 +122,7 @@ public class TurnAroundRange extends AbstractMeasurement {
protected EstimatedMeasurement theoreticalEvaluation(final int iteration, final int evaluation,
final SpacecraftState[] states) {
- final ObservableSatellite satellite = getSatellites().get(0);
- final SpacecraftState state = states[satellite.getPropagatorIndex()];
+ final SpacecraftState state = states[0];
// Turn around range derivatives are computed with respect to:
// - Spacecraft state in inertial frame
diff --git a/src/main/java/org/orekit/estimation/measurements/generation/AngularAzElBuilder.java b/src/main/java/org/orekit/estimation/measurements/generation/AngularAzElBuilder.java
index 7d37f45875438d67ed1ae5a04c796b437ae548e2..8d3d8abee73f6d4e55a78ba844091dc35722db22 100644
--- a/src/main/java/org/orekit/estimation/measurements/generation/AngularAzElBuilder.java
+++ b/src/main/java/org/orekit/estimation/measurements/generation/AngularAzElBuilder.java
@@ -57,10 +57,10 @@ public class AngularAzElBuilder extends AbstractMeasurementBuilder
final ObservableSatellite satellite = getSatellites()[0];
final double[] sigma = getTheoreticalStandardDeviation();
final double[] baseWeight = getBaseWeight();
- final SpacecraftState state = states[satellite.getPropagatorIndex()];
+ final SpacecraftState[] relevant = new SpacecraftState[] { states[satellite.getPropagatorIndex()] };
// create a dummy measurement
- final AngularAzEl dummy = new AngularAzEl(station, state.getDate(),
+ final AngularAzEl dummy = new AngularAzEl(station, relevant[0].getDate(),
new double[] {
0.0, 0.0
}, sigma, baseWeight, satellite);
@@ -78,7 +78,7 @@ public class AngularAzElBuilder extends AbstractMeasurementBuilder
}
// estimate the perfect value of the measurement
- final double[] angular = dummy.estimate(0, 0, states).getEstimatedValue();
+ final double[] angular = dummy.estimate(0, 0, relevant).getEstimatedValue();
// add the noise
final double[] noise = getNoise();
@@ -88,7 +88,7 @@ public class AngularAzElBuilder extends AbstractMeasurementBuilder
}
// generate measurement
- final AngularAzEl measurement = new AngularAzEl(station, state.getDate(), angular,
+ final AngularAzEl measurement = new AngularAzEl(station, relevant[0].getDate(), angular,
sigma, baseWeight, satellite);
for (final EstimationModifier modifier : getModifiers()) {
measurement.addModifier(modifier);
diff --git a/src/main/java/org/orekit/estimation/measurements/generation/AngularRaDecBuilder.java b/src/main/java/org/orekit/estimation/measurements/generation/AngularRaDecBuilder.java
index 2557dccfbc8255f4329f90204754c46bcb4a8820..26ce4c7e0647b361ff3e5194e19c67351ad7e1a5 100644
--- a/src/main/java/org/orekit/estimation/measurements/generation/AngularRaDecBuilder.java
+++ b/src/main/java/org/orekit/estimation/measurements/generation/AngularRaDecBuilder.java
@@ -63,10 +63,10 @@ public class AngularRaDecBuilder extends AbstractMeasurementBuilder modifier : getModifiers()) {
measurement.addModifier(modifier);
diff --git a/src/main/java/org/orekit/estimation/measurements/generation/InterSatellitesRangeBuilder.java b/src/main/java/org/orekit/estimation/measurements/generation/InterSatellitesRangeBuilder.java
index 11f05116921799a94c823d1ca369edf2ab236eff..5cad3e7a51616afc4c182a1b0c15bf8906459d29 100644
--- a/src/main/java/org/orekit/estimation/measurements/generation/InterSatellitesRangeBuilder.java
+++ b/src/main/java/org/orekit/estimation/measurements/generation/InterSatellitesRangeBuilder.java
@@ -57,6 +57,10 @@ public class InterSatellitesRangeBuilder extends AbstractMeasurementBuilder> {
List> getModifiers();
/** Generate a single measurement.
- * @param states spacecraft states
+ * @param states all spacecraft states (i.e. including ones that may not be relevant for the current builder)
* @return generated measurement
*/
T build(SpacecraftState[] states);
diff --git a/src/main/java/org/orekit/estimation/measurements/generation/PVBuilder.java b/src/main/java/org/orekit/estimation/measurements/generation/PVBuilder.java
index 2a56a109b28d50a01ad03db24940ee20524c36f8..9d24f739876b889cca9d2d027928f840eaab4b29 100644
--- a/src/main/java/org/orekit/estimation/measurements/generation/PVBuilder.java
+++ b/src/main/java/org/orekit/estimation/measurements/generation/PVBuilder.java
@@ -57,10 +57,10 @@ public class PVBuilder extends AbstractMeasurementBuilder {
final ObservableSatellite satellite = getSatellites()[0];
final double[] sigma = getTheoreticalStandardDeviation();
final double baseWeight = getBaseWeight()[0];
- final SpacecraftState state = states[satellite.getPropagatorIndex()];
+ final SpacecraftState[] relevant = new SpacecraftState[] { states[satellite.getPropagatorIndex()] };
// create a dummy measurement
- final PV dummy = new PV(state.getDate(), Vector3D.NaN, Vector3D.NaN,
+ final PV dummy = new PV(relevant[0].getDate(), Vector3D.NaN, Vector3D.NaN,
sigma[0], sigma[1], baseWeight, satellite);
for (final EstimationModifier modifier : getModifiers()) {
dummy.addModifier(modifier);
@@ -76,7 +76,7 @@ public class PVBuilder extends AbstractMeasurementBuilder {
}
// estimate the perfect value of the measurement
- final double[] pv = dummy.estimate(0, 0, states).getEstimatedValue();
+ final double[] pv = dummy.estimate(0, 0, relevant).getEstimatedValue();
// add the noise
final double[] noise = getNoise();
@@ -90,7 +90,7 @@ public class PVBuilder extends AbstractMeasurementBuilder {
}
// generate measurement
- final PV measurement = new PV(state.getDate(),
+ final PV measurement = new PV(relevant[0].getDate(),
new Vector3D(pv[0], pv[1], pv[2]), new Vector3D(pv[3], pv[4], pv[5]),
sigma[0], sigma[1], baseWeight, satellite);
for (final EstimationModifier modifier : getModifiers()) {
diff --git a/src/main/java/org/orekit/estimation/measurements/generation/PositionBuilder.java b/src/main/java/org/orekit/estimation/measurements/generation/PositionBuilder.java
index f4b4434684532640159c377f298bf010d08ca006..04b655ec90cc23463de9447f9f726de5866a4ec8 100644
--- a/src/main/java/org/orekit/estimation/measurements/generation/PositionBuilder.java
+++ b/src/main/java/org/orekit/estimation/measurements/generation/PositionBuilder.java
@@ -51,10 +51,10 @@ public class PositionBuilder extends AbstractMeasurementBuilder {
final ObservableSatellite satellite = getSatellites()[0];
final double sigma = getTheoreticalStandardDeviation()[0];
final double baseWeight = getBaseWeight()[0];
- final SpacecraftState state = states[satellite.getPropagatorIndex()];
+ final SpacecraftState[] relevant = new SpacecraftState[] { states[satellite.getPropagatorIndex()] };
// create a dummy measurement
- final Position dummy = new Position(state.getDate(), Vector3D.NaN, sigma, baseWeight, satellite);
+ final Position dummy = new Position(relevant[0].getDate(), Vector3D.NaN, sigma, baseWeight, satellite);
for (final EstimationModifier modifier : getModifiers()) {
dummy.addModifier(modifier);
}
@@ -69,7 +69,7 @@ public class PositionBuilder extends AbstractMeasurementBuilder {
}
// estimate the perfect value of the measurement
- final double[] position = dummy.estimate(0, 0, states).getEstimatedValue();
+ final double[] position = dummy.estimate(0, 0, relevant).getEstimatedValue();
// add the noise
final double[] noise = getNoise();
@@ -80,7 +80,7 @@ public class PositionBuilder extends AbstractMeasurementBuilder {
}
// generate measurement
- final Position measurement = new Position(state.getDate(), new Vector3D(position),
+ final Position measurement = new Position(relevant[0].getDate(), new Vector3D(position),
sigma, baseWeight, satellite);
for (final EstimationModifier modifier : getModifiers()) {
measurement.addModifier(modifier);
diff --git a/src/main/java/org/orekit/estimation/measurements/generation/RangeBuilder.java b/src/main/java/org/orekit/estimation/measurements/generation/RangeBuilder.java
index f3c5d205f84dbab0b8cc19cbd2fa37f98ffb5920..c0f54a109b86750038f87a860094a760ca9c4e42 100644
--- a/src/main/java/org/orekit/estimation/measurements/generation/RangeBuilder.java
+++ b/src/main/java/org/orekit/estimation/measurements/generation/RangeBuilder.java
@@ -62,10 +62,10 @@ public class RangeBuilder extends AbstractMeasurementBuilder {
final ObservableSatellite satellite = getSatellites()[0];
final double sigma = getTheoreticalStandardDeviation()[0];
final double baseWeight = getBaseWeight()[0];
- final SpacecraftState state = states[satellite.getPropagatorIndex()];
+ final SpacecraftState[] relevant = new SpacecraftState[] { states[satellite.getPropagatorIndex()] };
// create a dummy measurement
- final Range dummy = new Range(station, twoway, state.getDate(), Double.NaN, sigma, baseWeight, satellite);
+ final Range dummy = new Range(station, twoway, relevant[0].getDate(), Double.NaN, sigma, baseWeight, satellite);
for (final EstimationModifier modifier : getModifiers()) {
dummy.addModifier(modifier);
}
@@ -80,7 +80,7 @@ public class RangeBuilder extends AbstractMeasurementBuilder {
}
// estimate the perfect value of the measurement
- double range = dummy.estimate(0, 0, states).getEstimatedValue()[0];
+ double range = dummy.estimate(0, 0, relevant).getEstimatedValue()[0];
// add the noise
final double[] noise = getNoise();
@@ -89,7 +89,7 @@ public class RangeBuilder extends AbstractMeasurementBuilder {
}
// generate measurement
- final Range measurement = new Range(station, twoway, state.getDate(), range, sigma, baseWeight, satellite);
+ final Range measurement = new Range(station, twoway, relevant[0].getDate(), range, sigma, baseWeight, satellite);
for (final EstimationModifier modifier : getModifiers()) {
measurement.addModifier(modifier);
}
diff --git a/src/main/java/org/orekit/estimation/measurements/generation/RangeRateBuilder.java b/src/main/java/org/orekit/estimation/measurements/generation/RangeRateBuilder.java
index 4a653e3f3162b38cd4781814fd3dd1ce215d36ab..571365a510d165e928a35f971c333b70dbf09f68 100644
--- a/src/main/java/org/orekit/estimation/measurements/generation/RangeRateBuilder.java
+++ b/src/main/java/org/orekit/estimation/measurements/generation/RangeRateBuilder.java
@@ -62,10 +62,10 @@ public class RangeRateBuilder extends AbstractMeasurementBuilder {
final ObservableSatellite satellite = getSatellites()[0];
final double sigma = getTheoreticalStandardDeviation()[0];
final double baseWeight = getBaseWeight()[0];
- final SpacecraftState state = states[satellite.getPropagatorIndex()];
+ final SpacecraftState[] relevant = new SpacecraftState[] { states[satellite.getPropagatorIndex()] };
// create a dummy measurement
- final RangeRate dummy = new RangeRate(station, state.getDate(), Double.NaN, sigma, baseWeight, twoway, satellite);
+ final RangeRate dummy = new RangeRate(station, relevant[0].getDate(), Double.NaN, sigma, baseWeight, twoway, satellite);
for (final EstimationModifier modifier : getModifiers()) {
dummy.addModifier(modifier);
}
@@ -80,7 +80,7 @@ public class RangeRateBuilder extends AbstractMeasurementBuilder {
}
// estimate the perfect value of the measurement
- double rangeRate = dummy.estimate(0, 0, states).getEstimatedValue()[0];
+ double rangeRate = dummy.estimate(0, 0, relevant).getEstimatedValue()[0];
// add the noise
final double[] noise = getNoise();
@@ -89,7 +89,7 @@ public class RangeRateBuilder extends AbstractMeasurementBuilder {
}
// generate measurement
- final RangeRate measurement = new RangeRate(station, state.getDate(), rangeRate,
+ final RangeRate measurement = new RangeRate(station, relevant[0].getDate(), rangeRate,
sigma, baseWeight, twoway, satellite);
for (final EstimationModifier modifier : getModifiers()) {
measurement.addModifier(modifier);
diff --git a/src/main/java/org/orekit/estimation/measurements/generation/TurnAroundRangeBuilder.java b/src/main/java/org/orekit/estimation/measurements/generation/TurnAroundRangeBuilder.java
index bbb6621c487618f8a20a5b85e95cd19cead6ca1a..cbbdf8643cbca80c28aedf5e78d5c6c25808d099 100644
--- a/src/main/java/org/orekit/estimation/measurements/generation/TurnAroundRangeBuilder.java
+++ b/src/main/java/org/orekit/estimation/measurements/generation/TurnAroundRangeBuilder.java
@@ -62,10 +62,10 @@ public class TurnAroundRangeBuilder extends AbstractMeasurementBuilder modifier : getModifiers()) {
dummy.addModifier(modifier);
@@ -81,7 +81,7 @@ public class TurnAroundRangeBuilder extends AbstractMeasurementBuilder modifier : getModifiers()) {
measurement.addModifier(modifier);
diff --git a/src/main/java/org/orekit/estimation/measurements/gnss/AbstractDualFrequencyCombination.java b/src/main/java/org/orekit/estimation/measurements/gnss/AbstractDualFrequencyCombination.java
new file mode 100644
index 0000000000000000000000000000000000000000..f8f0add36e7da4052c22030df80893dbbeb96a60
--- /dev/null
+++ b/src/main/java/org/orekit/estimation/measurements/gnss/AbstractDualFrequencyCombination.java
@@ -0,0 +1,198 @@
+/* Copyright 2002-2019 CS Systèmes d'Information
+ * Licensed to CS Systèmes d'Information (CS) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.estimation.measurements.gnss;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.orekit.errors.OrekitException;
+import org.orekit.errors.OrekitMessages;
+import org.orekit.gnss.CombinedObservationData;
+import org.orekit.gnss.CombinedObservationDataSet;
+import org.orekit.gnss.Frequency;
+import org.orekit.gnss.MeasurementType;
+import org.orekit.gnss.ObservationData;
+import org.orekit.gnss.ObservationDataSet;
+import org.orekit.gnss.ObservationType;
+import org.orekit.gnss.SatelliteSystem;
+
+/** Base class for dual frequency combination of measurements.
+ * @author Bryan Cazabonne
+ * @since 10.1
+ */
+public abstract class AbstractDualFrequencyCombination implements MeasurementCombination {
+
+ /** Type of combination of measurements. */
+ private final CombinationType type;
+
+ /** Satellite system used for the combination. */
+ private final SatelliteSystem system;
+
+ /**
+ * Constructor.
+ * @param type combination of measurements type
+ * @param system satellite system
+ */
+ protected AbstractDualFrequencyCombination(final CombinationType type, final SatelliteSystem system) {
+ this.type = type;
+ this.system = system;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String getName() {
+ return type.getName();
+ }
+
+ /**
+ * Combines observation data using a dual frequency combination of measurements.
+ * @param od1 first observation data to combined
+ * @param od2 second observation data to combined
+ * @return a combined observation data
+ */
+ public CombinedObservationData combine(final ObservationData od1, final ObservationData od2) {
+
+ // Observation types
+ final ObservationType obsType1 = od1.getObservationType();
+ final ObservationType obsType2 = od2.getObservationType();
+
+ // Frequencies
+ final Frequency freq1 = obsType1.getFrequency(system);
+ final Frequency freq2 = obsType2.getFrequency(system);
+ // Check if the combination of measurements if performed for two different frequencies
+ if (freq1 == freq2) {
+ throw new OrekitException(OrekitMessages.INCOMPATIBLE_FREQUENCIES_FOR_COMBINATION_OF_MEASUREMENTS,
+ freq1, freq2, getName());
+ }
+
+ // Measurements types
+ final MeasurementType measType1 = obsType1.getMeasurementType();
+ final MeasurementType measType2 = obsType2.getMeasurementType();
+
+ // Check if measurement types are the same
+ if (measType1 != measType2) {
+ // If the measurement types are differents, an exception is thrown
+ throw new OrekitException(OrekitMessages.INVALID_MEASUREMENT_TYPES_FOR_COMBINATION_OF_MEASUREMENTS,
+ measType1, measType2, getName());
+ }
+
+ // Combined value
+ final double combinedValue = getCombinedValue(od1.getValue(), freq1, od2.getValue(), freq2);
+
+ // Combined frequency
+ final double combinedFrequency = getCombinedFrequency(freq1, freq2);
+
+ // Combined observation data
+ return new CombinedObservationData(type, measType1, combinedValue, combinedFrequency, Arrays.asList(od1, od2));
+
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public CombinedObservationDataSet combine(final ObservationDataSet observations) {
+
+ // Initialize list of measurements
+ final List pseudoRanges = new ArrayList<>();
+ final List phases = new ArrayList<>();
+
+ // Loop on observation data to fill lists
+ for (final ObservationData od : observations.getObservationData()) {
+ if (!Double.isNaN(od.getValue())) {
+ if (od.getObservationType().getMeasurementType() == MeasurementType.PSEUDO_RANGE) {
+ pseudoRanges.add(od);
+ } else if (od.getObservationType().getMeasurementType() == MeasurementType.CARRIER_PHASE) {
+ phases.add(od);
+ }
+ }
+ }
+
+ // Initialize list of combined observation data
+ final List combined = new ArrayList<>();
+ // Combine pseudo-ranges
+ for (int i = 0; i < pseudoRanges.size() - 1; i++) {
+ for (int j = 1; j < pseudoRanges.size(); j++) {
+ final boolean combine = isCombinationPossible(pseudoRanges.get(i), pseudoRanges.get(j));
+ if (combine) {
+ combined.add(combine(pseudoRanges.get(i), pseudoRanges.get(j)));
+ }
+ }
+ }
+ // Combine carrier-phases
+ for (int i = 0; i < phases.size() - 1; i++) {
+ for (int j = 1; j < phases.size(); j++) {
+ final boolean combine = isCombinationPossible(phases.get(i), phases.get(j));
+ if (combine) {
+ combined.add(combine(phases.get(i), phases.get(j)));
+ }
+ }
+ }
+
+ return new CombinedObservationDataSet(observations.getHeader(), observations.getSatelliteSystem(),
+ observations.getPrnNumber(), observations.getDate(),
+ observations.getRcvrClkOffset(), combined);
+ }
+
+ /**
+ * Get the combined observed value of two measurements.
+ * @param obs1 observed value of the first measurement
+ * @param f1 frequency of the first measurement
+ * @param obs2 observed value of the second measurement
+ * @param f2 frequency of the second measurement
+ * @return combined observed value
+ */
+ protected abstract double getCombinedValue(double obs1, Frequency f1, double obs2, Frequency f2);
+
+ /**
+ * Get the combined frequency of two measurements.
+ * @param f1 frequency of the first measurement
+ * @param f2 frequency of the second measurement
+ * @return combined frequency in MHz
+ */
+ protected abstract double getCombinedFrequency(Frequency f1, Frequency f2);
+
+ /**
+ * Verifies if two observation data can be combine.
+ * @param data1 first observation data
+ * @param data2 second observation data
+ * @return true if observation data can be combined
+ */
+ private boolean isCombinationPossible(final ObservationData data1, final ObservationData data2) {
+
+ // Observation types
+ final ObservationType obsType1 = data1.getObservationType();
+ final ObservationType obsType2 = data2.getObservationType();
+
+ // Geometry-Free combination is possible only if data frequencies are diffrents
+ if (obsType1.getFrequency(system) != obsType2.getFrequency(system)) {
+
+ if (obsType1.getSignalCode() == obsType2.getSignalCode()) {
+ // Observation code is the same. Combination of measurements can be performed
+ return true;
+ } else {
+ // Observation code is not the same. Combination of measurements can not be performed
+ return false;
+ }
+
+ } else {
+ // False because observation data have the same frequency
+ return false;
+ }
+
+ }
+
+}
diff --git a/src/main/java/org/orekit/estimation/measurements/gnss/AbstractSingleFrequencyCombination.java b/src/main/java/org/orekit/estimation/measurements/gnss/AbstractSingleFrequencyCombination.java
new file mode 100644
index 0000000000000000000000000000000000000000..727ae73d97868eaf01d6d844c5f3084b3bb3a974
--- /dev/null
+++ b/src/main/java/org/orekit/estimation/measurements/gnss/AbstractSingleFrequencyCombination.java
@@ -0,0 +1,197 @@
+/* Copyright 2002-2019 CS Systèmes d'Information
+ * Licensed to CS Systèmes d'Information (CS) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.estimation.measurements.gnss;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.orekit.errors.OrekitException;
+import org.orekit.errors.OrekitMessages;
+import org.orekit.gnss.CombinedObservationData;
+import org.orekit.gnss.CombinedObservationDataSet;
+import org.orekit.gnss.Frequency;
+import org.orekit.gnss.MeasurementType;
+import org.orekit.gnss.ObservationData;
+import org.orekit.gnss.ObservationDataSet;
+import org.orekit.gnss.ObservationType;
+import org.orekit.gnss.RinexHeader;
+import org.orekit.gnss.SatelliteSystem;
+
+/** Base class for single frequency combination of measurements.
+ * @author Bryan Cazabonne
+ * @since 10.1
+ */
+public abstract class AbstractSingleFrequencyCombination implements MeasurementCombination {
+
+ /** Type of combination of measurements. */
+ private final CombinationType type;
+
+ /** Satellite system used for the combination. */
+ private final SatelliteSystem system;
+
+ /**
+ * Constructor.
+ * @param type combination of measurements type
+ * @param system satellite system
+ */
+ protected AbstractSingleFrequencyCombination(final CombinationType type, final SatelliteSystem system) {
+ this.type = type;
+ this.system = system;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String getName() {
+ return type.getName();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public CombinedObservationDataSet combine(final ObservationDataSet observations) {
+
+ // Rinex file header
+ final RinexHeader header = observations.getHeader();
+ // Rinex version to integer
+ final int version = (int) header.getRinexVersion();
+
+ // Initialize list of measurements
+ final List pseudoRanges = new ArrayList<>();
+ final List phases = new ArrayList<>();
+
+ // Loop on observation data to fill lists
+ for (final ObservationData od : observations.getObservationData()) {
+ if (!Double.isNaN(od.getValue())) {
+ if (od.getObservationType().getMeasurementType() == MeasurementType.PSEUDO_RANGE) {
+ pseudoRanges.add(od);
+ } else if (od.getObservationType().getMeasurementType() == MeasurementType.CARRIER_PHASE) {
+ phases.add(od);
+ }
+ }
+ }
+
+ // Initialize list of combined observation data
+ final List combined = new ArrayList<>();
+
+ for (int i = 0; i < phases.size(); i++) {
+ for (int j = 0; j < pseudoRanges.size(); j++) {
+ final boolean combine = isCombinationPossible(version, phases.get(i), pseudoRanges.get(j));
+ if (combine) {
+ combined.add(combine(phases.get(i), pseudoRanges.get(j)));
+ }
+ }
+ }
+
+ return new CombinedObservationDataSet(observations.getHeader(), observations.getSatelliteSystem(),
+ observations.getPrnNumber(), observations.getDate(),
+ observations.getRcvrClkOffset(), combined);
+ }
+
+ /**
+ * Combines observation data using a single frequency combination of measurements.
+ * @param phase phase measurement
+ * @param pseudoRange pseudoRange measurement
+ * @return a combined observation data
+ */
+ public CombinedObservationData combine(final ObservationData phase, final ObservationData pseudoRange) {
+
+ // Observation types
+ final ObservationType obsType1 = phase.getObservationType();
+ final ObservationType obsType2 = pseudoRange.getObservationType();
+
+ // Frequencies
+ final Frequency freq1 = obsType1.getFrequency(system);
+ final Frequency freq2 = obsType2.getFrequency(system);
+ // Check if the combination of measurements if performed for two different frequencies
+ if (freq1 != freq2) {
+ throw new OrekitException(OrekitMessages.INCOMPATIBLE_FREQUENCIES_FOR_COMBINATION_OF_MEASUREMENTS,
+ freq1, freq2, getName());
+ }
+
+ // Measurements types
+ final MeasurementType measType1 = obsType1.getMeasurementType();
+ final MeasurementType measType2 = obsType2.getMeasurementType();
+
+ // Check if measurement types are the same
+ if (measType1 == measType2) {
+ // If the measurement types are the same, an exception is thrown
+ throw new OrekitException(OrekitMessages.INVALID_MEASUREMENT_TYPES_FOR_COMBINATION_OF_MEASUREMENTS,
+ measType1, measType2, getName());
+ }
+
+ // Frequency
+ final double f = freq1.getMHzFrequency();
+
+ // Combined value
+ final double combinedValue = getCombinedValue(phase.getValue(), pseudoRange.getValue(), f);
+
+ // Combined observation data
+ return new CombinedObservationData(CombinationType.PHASE_MINUS_CODE, MeasurementType.COMBINED_RANGE_PHASE,
+ combinedValue, f, Arrays.asList(phase, pseudoRange));
+ }
+
+ /**
+ * Get the combined observed value of two measurements.
+ * @param phase observed value of the phase measurement
+ * @param pseudoRange observed value of the range measurement
+ * @param f frequency of both measurements in MHz
+ * @return combined observed value
+ */
+ protected abstract double getCombinedValue(double phase, double pseudoRange, double f);
+
+ /**
+ * Verifies if two observation data can be combine.
+ * @param version Rinex file version (integer part)
+ * @param phase phase measurement
+ * @param pseudoRange pseudoRange measurement
+ * @return true if observation data can be combined
+ */
+ private boolean isCombinationPossible(final int version, final ObservationData phase, final ObservationData pseudoRange) {
+
+ // Observation types
+ final ObservationType obsType1 = phase.getObservationType();
+ final ObservationType obsType2 = pseudoRange.getObservationType();
+
+ // Phase minus Code combination is possible only if data frequencies are the same
+ if (obsType1.getFrequency(system) == obsType2.getFrequency(system)) {
+
+ // Switch on Rinex version
+ switch (version) {
+ case 2:
+ // Rinex 2 version
+ return true;
+ case 3:
+ if (obsType1.getSignalCode() == obsType2.getSignalCode()) {
+ // Observation code is the same. Combination of measurements can be performed
+ return true;
+ } else {
+ // Observation code is not the same. Combination of measurements can not be performed
+ return false;
+ }
+ default:
+ // Not supported Rinex version. Combination is not possible
+ return false;
+ }
+
+ } else {
+ // False because observation data have different frequency
+ return false;
+ }
+
+ }
+
+}
diff --git a/src/main/java/org/orekit/estimation/measurements/gnss/CombinationType.java b/src/main/java/org/orekit/estimation/measurements/gnss/CombinationType.java
new file mode 100644
index 0000000000000000000000000000000000000000..cf339b819e796b0f7e3a98be7f29f909046f7016
--- /dev/null
+++ b/src/main/java/org/orekit/estimation/measurements/gnss/CombinationType.java
@@ -0,0 +1,67 @@
+/* Copyright 2002-2019 CS Systèmes d'Information
+ * Licensed to CS Systèmes d'Information (CS) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.estimation.measurements.gnss;
+
+/**
+ * Enumerate for combination of measurements types.
+ *
+ * @author Bryan Cazabonne
+ * @since 10.1
+ */
+public enum CombinationType {
+
+ /** Phase minus code combination. */
+ PHASE_MINUS_CODE("Phase minus code"),
+
+ /** GRoup And Phase Ionospheric Calibration (GRAPHIC) combination. */
+ GRAPHIC("GRAPHIC"),
+
+ /** Geometry-free combination. */
+ GEOMETRY_FREE("Geometry Free"),
+
+ /** Ionosphere-free combination. */
+ IONO_FREE("Ionosphere Free"),
+
+ /** Narrow-lane combination. */
+ NARROW_LANE("Narrow Lane"),
+
+ /** Wide-lane combination. */
+ WIDE_LANE("Wide Lane"),
+
+ /** Melbourne-Wübbena combination. */
+ MELBOURNE_WUBBENA("Melbourne Wubbena");
+
+ /** Name of the combination of measurements. */
+ private final String name;
+
+ /**
+ * Constructor.
+ * @param name name of the combination of measurements
+ */
+ CombinationType(final String name) {
+ this.name = name;
+ }
+
+ /**
+ * Get the name of the combination of measurements.
+ * @return the name
+ */
+ public String getName() {
+ return name;
+ }
+
+}
diff --git a/src/main/java/org/orekit/estimation/measurements/gnss/GRAPHICCombination.java b/src/main/java/org/orekit/estimation/measurements/gnss/GRAPHICCombination.java
new file mode 100644
index 0000000000000000000000000000000000000000..50847ac0f8b9f69518cb9197a14f779060e5745c
--- /dev/null
+++ b/src/main/java/org/orekit/estimation/measurements/gnss/GRAPHICCombination.java
@@ -0,0 +1,57 @@
+/* Copyright 2002-2019 CS Systèmes d'Information
+ * Licensed to CS Systèmes d'Information (CS) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.estimation.measurements.gnss;
+
+import org.orekit.gnss.SatelliteSystem;
+
+/**
+ * GRoup And Phase Ionospheric Calibration (GRAPHIC) combination.
+ *
+ * This combination is a ionosphere-free single frequency
+ * combination of measurements.
+ *
+ *
+ * mf = 0.5 * (Φf + Rf)
+ *
+ * With:
+ *
+ * - mf : GRAPHIC measurement.
+ * - Φf : Phase measurement.
+ * - Rf : Code measurement.
+ * - f : Frequency.
+ *
+ * @author Bryan Cazabonne
+ * @since 10.1
+ */
+public class GRAPHICCombination extends AbstractSingleFrequencyCombination {
+
+ /**
+ * Package private constructor for the factory.
+ * @param system satellite system for wich the combination is applied
+ */
+ GRAPHICCombination(final SatelliteSystem system) {
+ super(CombinationType.GRAPHIC, system);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected double getCombinedValue(final double phase, final double pseudoRange, final double f) {
+ // Combination does not depend on the frequency
+ return 0.5 * (phase + pseudoRange);
+ }
+
+}
diff --git a/src/main/java/org/orekit/estimation/measurements/gnss/GeometryFreeCombination.java b/src/main/java/org/orekit/estimation/measurements/gnss/GeometryFreeCombination.java
new file mode 100644
index 0000000000000000000000000000000000000000..1e733c2840029b3e132b36734084aaf1fe7aced6
--- /dev/null
+++ b/src/main/java/org/orekit/estimation/measurements/gnss/GeometryFreeCombination.java
@@ -0,0 +1,72 @@
+/* Copyright 2002-2019 CS Systèmes d'Information
+ * Licensed to CS Systèmes d'Information (CS) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.estimation.measurements.gnss;
+
+import org.hipparchus.util.FastMath;
+import org.orekit.gnss.Frequency;
+import org.orekit.gnss.MeasurementType;
+import org.orekit.gnss.SatelliteSystem;
+
+/**
+ * Geometry-free combination.
+ *
+ * This combination removes the geometry part of the measurement.
+ * It can be used to estimate the ionospheric electron content or to detect
+ * cycle slips in the carrier phase, as well.
+ *
+ *
+ * mGF = m2 - m1
+ *
+ * With:
+ *
+ * - mGF: Geometry-free measurement.
+ * - m1 : First measurement.
+ * - m1 : Second measurement.
+ *
+ *
+ * Geometry-Free combination is a dual frequency combination.
+ * The two measurements shall have different frequencies but they must have the same {@link MeasurementType}.
+ *
+ * @author Bryan Cazabonne
+ * @since 10.1
+ */
+public class GeometryFreeCombination extends AbstractDualFrequencyCombination {
+
+ /**
+ * Package private constructor for the factory.
+ * @param system satellite system for wich the combination is applied
+ */
+ GeometryFreeCombination(final SatelliteSystem system) {
+ super(CombinationType.GEOMETRY_FREE, system);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected double getCombinedValue(final double obs1, final Frequency f1,
+ final double obs2, final Frequency f2) {
+ // Combined observed value does not depend on frequency for the Geometry-Free combination
+ return FastMath.abs(obs2 - obs1);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected double getCombinedFrequency(final Frequency f1, final Frequency f2) {
+ // There is not combined frequency for the Geometry-Free combination
+ return Double.NaN;
+ }
+
+}
diff --git a/src/main/java/org/orekit/estimation/measurements/gnss/IntegerLeastSquareSolution.java b/src/main/java/org/orekit/estimation/measurements/gnss/IntegerLeastSquareSolution.java
index bcb5fb40a32864f51a00e3a7be8fc81490f32b63..57f2d5887000b992684edb99a918338288f0d861 100644
--- a/src/main/java/org/orekit/estimation/measurements/gnss/IntegerLeastSquareSolution.java
+++ b/src/main/java/org/orekit/estimation/measurements/gnss/IntegerLeastSquareSolution.java
@@ -57,4 +57,29 @@ public class IntegerLeastSquareSolution implements Comparable
+ * This combination removes the first order (up to 99.9%)
+ * ionospheric effect.
+ *
+ *
+ * f1² * m1 - f2² * m2
+ * mIF = -----------------------
+ * f1² - f2²
+ *
+ * With:
+ *
+ * - mIF: Ionosphere-free measurement.
+ * - f1 : Frequency of the first measurement.
+ * - m1 : First measurement.
+ * - f2 : Frequency of the second measurement.
+ * - m1 : Second measurement.
+ *
+ *
+ * Ionosphere-free combination is a dual frequency combination.
+ * The two measurements shall have different frequencies but they must have the same {@link MeasurementType}.
+ *
+ * @author Bryan Cazabonne
+ * @since 10.1
+ */
+public class IonosphereFreeCombination extends AbstractDualFrequencyCombination {
+
+ /**
+ * Package private constructor for the factory.
+ * @param system satellite system for wich the combination is applied
+ */
+ IonosphereFreeCombination(final SatelliteSystem system) {
+ super(CombinationType.IONO_FREE, system);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected double getCombinedValue(final double obs1, final Frequency f1,
+ final double obs2, final Frequency f2) {
+ // Get the ration f/f0
+ final double ratioF1 = f1.getRatio();
+ final double ratioF2 = f2.getRatio();
+ final double ratioF1Sq = ratioF1 * ratioF1;
+ final double ratioF2Sq = ratioF2 * ratioF2;
+ // Perform combination
+ return MathArrays.linearCombination(ratioF1Sq, obs1, -ratioF2Sq, obs2) / (ratioF1Sq - ratioF2Sq);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected double getCombinedFrequency(final Frequency f1, final Frequency f2) {
+ // Get the ratios f/f0
+ final double ratioF1 = f1.getRatio();
+ final double ratioF2 = f2.getRatio();
+ // Multiplication factor used to compute the combined frequency
+ int k = 1;
+ // Get the integer part of the ratios
+ final int ratioF1Int = (int) ratioF1;
+ final int ratioF2Int = (int) ratioF2;
+ // Check if the ratios are composed of a decimal part
+ if (ratioF1 - ratioF1Int > 0.0 || ratioF2 - ratioF2Int > 0.0) {
+ // Do nothing, k remains equal to 1
+ } else {
+ // k is the GCD of the interger ratio
+ k = ArithmeticUtils.gcd(ratioF1Int, ratioF2Int);
+ }
+ // Combined frequency
+ return MathArrays.linearCombination(ratioF1, ratioF1, -ratioF2, ratioF2) * (Frequency.F0 / k);
+ }
+
+}
diff --git a/src/main/java/org/orekit/estimation/measurements/gnss/MeasurementCombination.java b/src/main/java/org/orekit/estimation/measurements/gnss/MeasurementCombination.java
new file mode 100644
index 0000000000000000000000000000000000000000..44c078f86bb7b4e491ec3d0ab1b2419f0f52c30b
--- /dev/null
+++ b/src/main/java/org/orekit/estimation/measurements/gnss/MeasurementCombination.java
@@ -0,0 +1,42 @@
+/* Copyright 2002-2019 CS Systèmes d'Information
+ * Licensed to CS Systèmes d'Information (CS) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.estimation.measurements.gnss;
+
+import org.orekit.gnss.CombinedObservationDataSet;
+import org.orekit.gnss.ObservationDataSet;
+
+/**
+ * Interface for combination of measurements.
+ * @author Bryan Cazabonne
+ * @since 10.1
+ */
+public interface MeasurementCombination {
+
+ /**
+ * Combines observation data using a combination of measurements.
+ * @param observations observation data set
+ * @return a combined observation data set
+ */
+ CombinedObservationDataSet combine(ObservationDataSet observations);
+
+ /**
+ * Get the name of the combination of measurements.
+ * @return name of the combination of measurements
+ */
+ String getName();
+
+}
diff --git a/src/main/java/org/orekit/estimation/measurements/gnss/MeasurementCombinationFactory.java b/src/main/java/org/orekit/estimation/measurements/gnss/MeasurementCombinationFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..bc2c0ebeaf537dccd479c7622edd8cc867ddd602
--- /dev/null
+++ b/src/main/java/org/orekit/estimation/measurements/gnss/MeasurementCombinationFactory.java
@@ -0,0 +1,101 @@
+/* Copyright 2002-2019 CS Systèmes d'Information
+ * Licensed to CS Systèmes d'Information (CS) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.estimation.measurements.gnss;
+
+import org.orekit.gnss.SatelliteSystem;
+
+/** Factory for predefined combination of measurements.
+ *
+ * This is a utility class, so its constructor is private.
+ *
+ * @author Bryan Cazabonne
+ * @since 10.1
+ */
+public class MeasurementCombinationFactory {
+
+ /** Private constructor.
+ * This class is a utility class, it should neither have a public
+ * nor a default constructor. This private constructor prevents
+ * the compiler from generating one automatically.
+ */
+ private MeasurementCombinationFactory() {
+ }
+
+ /**
+ * Get the Wide-Lane combination of measurements.
+ * @param system satellite system
+ * @return Wide-Lane combination
+ */
+ public static WideLaneCombination getWideLaneCombination(final SatelliteSystem system) {
+ return new WideLaneCombination(system);
+ }
+
+ /**
+ * Get the Narrow-Lane combination of measurements.
+ * @param system satellite system
+ * @return Narrow-Lane combination
+ */
+ public static NarrowLaneCombination getNarrowLaneCombination(final SatelliteSystem system) {
+ return new NarrowLaneCombination(system);
+ }
+
+ /**
+ * Get the Ionosphere-Free combination of measurements.
+ * @param system satellite system
+ * @return Ionosphere-Lane combination
+ */
+ public static IonosphereFreeCombination getIonosphereFreeCombination(final SatelliteSystem system) {
+ return new IonosphereFreeCombination(system);
+ }
+
+ /**
+ * Get the Geometry-Free combination of measurements.
+ * @param system satellite system
+ * @return Geometry-Free combination
+ */
+ public static GeometryFreeCombination getGeometryFreeCombination(final SatelliteSystem system) {
+ return new GeometryFreeCombination(system);
+ }
+
+ /**
+ * Get the Melbourne-Wübbena combination of measurements.
+ * @param system satellite system
+ * @return Melbourne-Wübbena combination
+ */
+ public static MelbourneWubbenaCombination getMelbourneWubbenaCombination(final SatelliteSystem system) {
+ return new MelbourneWubbenaCombination(system);
+ }
+
+ /**
+ * Get the phase minus code combination of measurements.
+ * @param system satellite system
+ * @return phase minus code combination
+ */
+ public static PhaseMinusCodeCombination getPhaseMinusCodeCombination(final SatelliteSystem system) {
+ return new PhaseMinusCodeCombination(system);
+ }
+
+ /**
+ * Get the GRAPHIC combination of measurements.
+ * @param system satellite system
+ * @return phase minus code combination
+ */
+ public static GRAPHICCombination getGRAPHICCombination(final SatelliteSystem system) {
+ return new GRAPHICCombination(system);
+ }
+
+}
diff --git a/src/main/java/org/orekit/estimation/measurements/gnss/MelbourneWubbenaCombination.java b/src/main/java/org/orekit/estimation/measurements/gnss/MelbourneWubbenaCombination.java
new file mode 100644
index 0000000000000000000000000000000000000000..4cb9403aa1146b6a2c0e3af192e6f5982ea3f0d5
--- /dev/null
+++ b/src/main/java/org/orekit/estimation/measurements/gnss/MelbourneWubbenaCombination.java
@@ -0,0 +1,166 @@
+/* Copyright 2002-2019 CS Systèmes d'Information
+ * Licensed to CS Systèmes d'Information (CS) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.estimation.measurements.gnss;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.hipparchus.util.FastMath;
+import org.orekit.gnss.CombinedObservationData;
+import org.orekit.gnss.CombinedObservationDataSet;
+import org.orekit.gnss.MeasurementType;
+import org.orekit.gnss.ObservationData;
+import org.orekit.gnss.ObservationDataSet;
+import org.orekit.gnss.SatelliteSystem;
+
+/**
+ * Melbourne-Wübbena combination.
+ *
+ * This combination allows, thanks to the wide-lane combination, a larger wavelength
+ * than each signal individually. Moreover, the measurement noise is reduced by the
+ * narrow-lane combination of code measurements.
+ *
+ *
+ * mMW = ΦWL - RNL
+ * mMW = λWL * NWL + b + ε
+ *
+ * With:
+ *
+ * - mMW : Melbourne-Wübbena measurement.
+ * - ΦWL : Wide-Lane phase measurement.
+ * - RNL : Narrow-Lane code measurement.
+ * - λWL : Wide-Lane wavelength.
+ * - NWL : Wide-Lane ambiguity (Nf1 - Nf2).
+ * - b : Satellite and receiver instrumental delays.
+ * - ε : Measurement noise.
+ *
+ *
+ * {@link NarrowLaneCombination Narrow-Lane} and {@link WideLaneCombination Wide-Lane}
+ * combinations shall be performed with the same pair of frequencies.
+ *
+ *
+ * @see "Detector based in code and carrier phase data: The Melbourne-Wübbena combination,
+ * J. Sanz Subirana, J.M. Juan Zornoza and M. Hernández-Pajares, 2011"
+ *
+ * @author Bryan Cazabonne
+ * @since 10.1
+ */
+public class MelbourneWubbenaCombination implements MeasurementCombination {
+
+ /** Threshold for frequency comparison. */
+ private static final double THRESHOLD = 1.0e-10;
+
+ /** Satellite system used for the combination. */
+ private final SatelliteSystem system;
+
+ /**
+ * Package private constructor for the factory.
+ * @param system satellite system for wich the combination is applied
+ */
+ MelbourneWubbenaCombination(final SatelliteSystem system) {
+ this.system = system;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public CombinedObservationDataSet combine(final ObservationDataSet observations) {
+
+ // Wide-Lane combination
+ final WideLaneCombination wideLane = MeasurementCombinationFactory.getWideLaneCombination(system);
+ final CombinedObservationDataSet combinedWL = wideLane.combine(observations);
+
+ // Narrow-Lane combination
+ final NarrowLaneCombination narrowLane = MeasurementCombinationFactory.getNarrowLaneCombination(system);
+ final CombinedObservationDataSet combinedNL = narrowLane.combine(observations);
+
+ // Initialize list of combined observation data
+ final List combined = new ArrayList<>();
+
+ // Loop on Wide-Lane measurements
+ for (CombinedObservationData odWL : combinedWL.getObservationData()) {
+ // Only consider combined phase measurements
+ if (odWL.getMeasurementType() == MeasurementType.CARRIER_PHASE) {
+ // Loop on Narrow-Lane measurements
+ for (CombinedObservationData odNL : combinedNL.getObservationData()) {
+ // Only consider combined range measurements
+ if (odNL.getMeasurementType() == MeasurementType.PSEUDO_RANGE) {
+ // Verify if the combinations have used the same frequencies
+ final boolean isCombinationPossible = isCombinationPossible(odWL, odNL);
+ if (isCombinationPossible) {
+ // Combined value and frequency
+ final double combinedValue = odWL.getValue() - odNL.getValue();
+ final double combinedFrequency = odWL.getCombinedMHzFrequency();
+ // Used observation data to build the Melbourn-Wübbena measurement
+ final List usedData = new ArrayList(4);
+ usedData.add(0, odWL.getUsedObservationData().get(0));
+ usedData.add(1, odWL.getUsedObservationData().get(1));
+ usedData.add(2, odNL.getUsedObservationData().get(0));
+ usedData.add(3, odNL.getUsedObservationData().get(1));
+ // Update the combined observation data list
+ combined.add(new CombinedObservationData(CombinationType.MELBOURNE_WUBBENA,
+ MeasurementType.COMBINED_RANGE_PHASE,
+ combinedValue, combinedFrequency, usedData));
+ }
+ }
+ }
+ }
+ }
+
+ return new CombinedObservationDataSet(observations.getHeader(), observations.getSatelliteSystem(),
+ observations.getPrnNumber(), observations.getDate(),
+ observations.getRcvrClkOffset(), combined);
+ }
+
+ /**
+ * Verifies if the Melbourne-Wübbena combination is possible between both combined observation data.
+ *
+ * This method compares the frequencies of the combined measurement to decide
+ * if the combination of measurements is possible.
+ * The combination is possible if :
+ *
+ * abs(f1WL - f2WL) = abs(f1NL - f2NL)
+ *
+ *
+ * @param odWL Wide-Lane measurement
+ * @param odNL Narrow-Lane measurement
+ * @return true if the Melbourne-Wübbena combination is possible
+ */
+ private boolean isCombinationPossible(final CombinedObservationData odWL, final CombinedObservationData odNL) {
+ // Frequencies
+ final double[] frequency = new double[4];
+ int j = 0;
+ for (int i = 0; i < odWL.getUsedObservationData().size(); i++) {
+ frequency[j++] = odWL.getUsedObservationData().get(i).getObservationType().getFrequency(system).getMHzFrequency();
+ frequency[j++] = odNL.getUsedObservationData().get(i).getObservationType().getFrequency(system).getMHzFrequency();
+ }
+ // Verify if used frequencies are the same.
+ // Possible numerical error is taken into account by using a threshold of acceptance
+ if (FastMath.abs(frequency[0] - frequency[2]) - FastMath.abs(frequency[1] - frequency[3]) < THRESHOLD) {
+ // Frequencies used for the combinations are the same
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String getName() {
+ return CombinationType.MELBOURNE_WUBBENA.getName();
+ }
+
+}
diff --git a/src/main/java/org/orekit/estimation/measurements/gnss/NarrowLaneCombination.java b/src/main/java/org/orekit/estimation/measurements/gnss/NarrowLaneCombination.java
new file mode 100644
index 0000000000000000000000000000000000000000..19272d390625483c103ca9246022e06e8816672e
--- /dev/null
+++ b/src/main/java/org/orekit/estimation/measurements/gnss/NarrowLaneCombination.java
@@ -0,0 +1,78 @@
+/* Copyright 2002-2019 CS Systèmes d'Information
+ * Licensed to CS Systèmes d'Information (CS) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.estimation.measurements.gnss;
+
+import org.hipparchus.util.MathArrays;
+import org.orekit.gnss.Frequency;
+import org.orekit.gnss.MeasurementType;
+import org.orekit.gnss.SatelliteSystem;
+
+/**
+ * Narrow-Lane combination.
+ *
+ * This combination create signal with a narrow wavelength.
+ * The signal in this combination has a lower noise than each
+ * separated separeted component.
+ *
+ *
+ * f1 * m1 + f2 * m2
+ * mNL = -----------------------
+ * f1 + f2
+ *
+ * With:
+ *
+ * - mNL : Narrow-laning measurement.
+ * - f1 : Frequency of the first measurement.
+ * - pr1 : First measurement.
+ * - f2 : Frequency of the second measurement.
+ * - m1 : Second measurement.
+ *
+ *
+ * Narrow-Lane combination is a dual frequency combination.
+ * The two measurements shall have different frequencies but they must have the same {@link MeasurementType}.
+ *
+ * @author Bryan Cazabonne
+ * @since 10.1
+ */
+public class NarrowLaneCombination extends AbstractDualFrequencyCombination {
+
+ /**
+ * Package private constructor for the factory.
+ * @param system satellite system for wich the combination is applied
+ */
+ NarrowLaneCombination(final SatelliteSystem system) {
+ super(CombinationType.NARROW_LANE, system);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected double getCombinedValue(final double obs1, final Frequency f1,
+ final double obs2, final Frequency f2) {
+ // Get the ration f/f0
+ final double ratioF1 = f1.getRatio();
+ final double ratioF2 = f2.getRatio();
+ // Perform combination
+ return MathArrays.linearCombination(ratioF1, obs1, ratioF2, obs2) / (ratioF1 + ratioF2);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected double getCombinedFrequency(final Frequency f1, final Frequency f2) {
+ return f1.getMHzFrequency() + f2.getMHzFrequency();
+ }
+
+}
diff --git a/src/main/java/org/orekit/estimation/measurements/gnss/Phase.java b/src/main/java/org/orekit/estimation/measurements/gnss/Phase.java
index fca415614c1ec766fef5bb671125208caf2ad6df..5191b9d5507c3c3793b202139ec2b3bf7814d5d0 100644
--- a/src/main/java/org/orekit/estimation/measurements/gnss/Phase.java
+++ b/src/main/java/org/orekit/estimation/measurements/gnss/Phase.java
@@ -69,7 +69,7 @@ public class Phase extends AbstractMeasurement {
/** Simple constructor.
* @param station ground station from which measurement is performed
* @param date date of the measurement
- * @param phase observed value
+ * @param phase observed value (cycles)
* @param wavelength phase observed value wavelength (m)
* @param sigma theoretical standard deviation
* @param baseWeight base weight
diff --git a/src/main/java/org/orekit/estimation/measurements/gnss/PhaseBuilder.java b/src/main/java/org/orekit/estimation/measurements/gnss/PhaseBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..0e3c245766ab171d380e9ed787dfdbf710cb349e
--- /dev/null
+++ b/src/main/java/org/orekit/estimation/measurements/gnss/PhaseBuilder.java
@@ -0,0 +1,100 @@
+/* Copyright 2002-2019 CS Systèmes d'Information
+ * Licensed to CS Systèmes d'Information (CS) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.estimation.measurements.gnss;
+
+import org.hipparchus.random.CorrelatedRandomVectorGenerator;
+import org.orekit.estimation.measurements.EstimationModifier;
+import org.orekit.estimation.measurements.GroundStation;
+import org.orekit.estimation.measurements.ObservableSatellite;
+import org.orekit.estimation.measurements.generation.AbstractMeasurementBuilder;
+import org.orekit.propagation.SpacecraftState;
+import org.orekit.time.AbsoluteDate;
+import org.orekit.utils.ParameterDriver;
+
+
+/** Builder for {@link Phase} measurements.
+ * @author Luc Maisonobe
+ * @since 10.1
+ */
+public class PhaseBuilder extends AbstractMeasurementBuilder {
+
+ /** Ground station from which measurement is performed. */
+ private final GroundStation station;
+
+ /** Wavelength of the phase observed value [m]. */
+ private final double wavelength;
+
+ /** Simple constructor.
+ * @param noiseSource noise source, may be null for generating perfect measurements
+ * @param station ground station from which measurement is performed
+ * @param wavelength phase observed value wavelength (m)
+ * @param sigma theoretical standard deviation
+ * @param baseWeight base weight
+ * @param satellite satellite related to this builder
+ */
+ public PhaseBuilder(final CorrelatedRandomVectorGenerator noiseSource,
+ final GroundStation station, final double wavelength,
+ final double sigma, final double baseWeight,
+ final ObservableSatellite satellite) {
+ super(noiseSource, sigma, baseWeight, satellite);
+ this.station = station;
+ this.wavelength = wavelength;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Phase build(final SpacecraftState[] states) {
+
+ final ObservableSatellite satellite = getSatellites()[0];
+ final double sigma = getTheoreticalStandardDeviation()[0];
+ final double baseWeight = getBaseWeight()[0];
+ final SpacecraftState[] relevant = new SpacecraftState[] { states[satellite.getPropagatorIndex()] };
+
+ // create a dummy measurement
+ final Phase dummy = new Phase(station, relevant[0].getDate(), Double.NaN, wavelength, sigma, baseWeight, satellite);
+ for (final EstimationModifier modifier : getModifiers()) {
+ dummy.addModifier(modifier);
+ }
+
+ // set a reference date for parameters missing one
+ for (final ParameterDriver driver : dummy.getParametersDrivers()) {
+ if (driver.getReferenceDate() == null) {
+ final AbsoluteDate start = getStart();
+ final AbsoluteDate end = getEnd();
+ driver.setReferenceDate(start.durationFrom(end) <= 0 ? start : end);
+ }
+ }
+
+ // estimate the perfect value of the measurement
+ double phase = dummy.estimate(0, 0, relevant).getEstimatedValue()[0];
+
+ // add the noise
+ final double[] noise = getNoise();
+ if (noise != null) {
+ phase += noise[0];
+ }
+
+ // generate measurement
+ final Phase measurement = new Phase(station, relevant[0].getDate(), phase, wavelength, sigma, baseWeight, satellite);
+ for (final EstimationModifier modifier : getModifiers()) {
+ measurement.addModifier(modifier);
+ }
+ return measurement;
+
+ }
+
+}
diff --git a/src/main/java/org/orekit/estimation/measurements/gnss/PhaseMinusCodeCombination.java b/src/main/java/org/orekit/estimation/measurements/gnss/PhaseMinusCodeCombination.java
new file mode 100644
index 0000000000000000000000000000000000000000..7c1df7d3f9619d7ac235f36d7c48bc6495701992
--- /dev/null
+++ b/src/main/java/org/orekit/estimation/measurements/gnss/PhaseMinusCodeCombination.java
@@ -0,0 +1,57 @@
+/* Copyright 2002-2019 CS Systèmes d'Information
+ * Licensed to CS Systèmes d'Information (CS) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.estimation.measurements.gnss;
+
+import org.orekit.gnss.SatelliteSystem;
+
+/**
+ * Phase minus Code combination.
+ *
+ * This combination is a single frequency combination of
+ * measurements that can be used for cycle-slip detection.
+ *
+ *
+ * mf = Φf - Rf
+ *
+ * With:
+ *
+ * - mf : Phase minus Code measurement.
+ * - Φf : Phase measurement.
+ * - Rf : Code measurement.
+ * - f : Frequency.
+ *
+ * @author Bryan Cazabonne
+ * @since 10.1
+ */
+public class PhaseMinusCodeCombination extends AbstractSingleFrequencyCombination {
+
+ /**
+ * Package private constructor for the factory.
+ * @param system satellite system for wich the combination is applied
+ */
+ PhaseMinusCodeCombination(final SatelliteSystem system) {
+ super(CombinationType.PHASE_MINUS_CODE, system);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected double getCombinedValue(final double phase, final double pseudoRange, final double f) {
+ // Combination does not depend on the frequency
+ return phase - pseudoRange;
+ }
+
+}
diff --git a/src/main/java/org/orekit/estimation/measurements/gnss/WideLaneCombination.java b/src/main/java/org/orekit/estimation/measurements/gnss/WideLaneCombination.java
new file mode 100644
index 0000000000000000000000000000000000000000..519bea981563c2320692b2f1b0f508ba6cdbfb97
--- /dev/null
+++ b/src/main/java/org/orekit/estimation/measurements/gnss/WideLaneCombination.java
@@ -0,0 +1,79 @@
+/* Copyright 2002-2019 CS Systèmes d'Information
+ * Licensed to CS Systèmes d'Information (CS) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.estimation.measurements.gnss;
+
+import org.hipparchus.util.MathArrays;
+import org.orekit.gnss.Frequency;
+import org.orekit.gnss.MeasurementType;
+import org.orekit.gnss.SatelliteSystem;
+
+/**
+ * Wide-Lane combination.
+ *
+ * This combination are used to create a signal
+ * with a significantly wide wavelength.
+ * This longer wavelength is useful for cycle-slips
+ * detection and ambiguity fixing
+ *
+ *
+ * f1 * m1 - f2 * m2
+ * mWL = -----------------------
+ * f1 - f2
+ *
+ * With:
+ *
+ * - mWL: Wide-laning measurement.
+ * - f1 : Frequency of the first measurement.
+ * - m1 : First measurement.
+ * - f2 : Frequency of the second measurement.
+ * - m1 : Second measurement.
+ *
+ *
+ * Wide-Lane combination is a dual frequency combination.
+ * The two measurements shall have different frequencies but they must have the same {@link MeasurementType}.
+ *
+ * @author Bryan Cazabonne
+ * @since 10.1
+ */
+public class WideLaneCombination extends AbstractDualFrequencyCombination {
+
+ /**
+ * Package private constructor for the factory.
+ * @param system satellite system for wich the combination is applied
+ */
+ WideLaneCombination(final SatelliteSystem system) {
+ super(CombinationType.WIDE_LANE, system);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected double getCombinedValue(final double obs1, final Frequency f1,
+ final double obs2, final Frequency f2) {
+ // Get the ration f/f0
+ final double ratioF1 = f1.getRatio();
+ final double ratioF2 = f2.getRatio();
+ // Perform combination
+ return MathArrays.linearCombination(ratioF1, obs1, -ratioF2, obs2) / (ratioF1 - ratioF2);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected double getCombinedFrequency(final Frequency f1, final Frequency f2) {
+ return f1.getMHzFrequency() - f2.getMHzFrequency();
+ }
+
+}
diff --git a/src/main/java/org/orekit/estimation/measurements/gnss/WindUp.java b/src/main/java/org/orekit/estimation/measurements/gnss/WindUp.java
new file mode 100644
index 0000000000000000000000000000000000000000..c32bc4e618c95cb74992cb2bfef83276021462bd
--- /dev/null
+++ b/src/main/java/org/orekit/estimation/measurements/gnss/WindUp.java
@@ -0,0 +1,106 @@
+/* Copyright 2002-2019 CS Systèmes d'Information
+ * Licensed to CS Systèmes d'Information (CS) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.estimation.measurements.gnss;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.hipparchus.geometry.euclidean.threed.Rotation;
+import org.hipparchus.geometry.euclidean.threed.Vector3D;
+import org.hipparchus.util.FastMath;
+import org.hipparchus.util.MathUtils;
+import org.orekit.estimation.measurements.EstimatedMeasurement;
+import org.orekit.estimation.measurements.EstimationModifier;
+import org.orekit.estimation.measurements.GroundStation;
+import org.orekit.frames.Frame;
+import org.orekit.utils.ParameterDriver;
+import org.orekit.utils.TimeStampedPVCoordinates;
+
+/** Modifier for wind-up effect in GNSS {@link Phase phase measurements}.
+ * @see Carrier Phase Wind-up Effect
+ * @see WindUpFactory
+ * @author Luc Maisonobe
+ * @since 10.1
+ */
+public class WindUp implements EstimationModifier {
+
+ /** Cached angular value of wind-up. */
+ private double angularWindUp;
+
+ /** Simple constructor.
+ *
+ * The constructor is package protected to enforce use of {@link WindUpFactory}
+ * and preserve phase continuity for successive measurements involving the same
+ * satellite/receiver pair.
+ *
+ */
+ WindUp() {
+ angularWindUp = 0.0;
+ }
+
+ /** {@inheritDoc}
+ *
+ * Wind-up effect has no parameters, the returned list is always empty.
+ *
+ */
+ @Override
+ public List getParametersDrivers() {
+ return Collections.emptyList();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void modify(final EstimatedMeasurement estimated) {
+
+ // signal line of sight
+ final TimeStampedPVCoordinates[] participants = estimated.getParticipants();
+ final Vector3D los = participants[1].getPosition().subtract(participants[0].getPosition()).normalize();
+
+ // get ground antenna dipole
+ final Frame inertial = estimated.getStates()[0].getFrame();
+ final GroundStation station = estimated.getObservedMeasurement().getStation();
+ final Rotation offsetToInert = station.getOffsetToInertial(inertial, estimated.getDate()).getRotation();
+ final Vector3D iGround = offsetToInert.applyTo(Vector3D.PLUS_I);
+ final Vector3D jGround = offsetToInert.applyTo(Vector3D.PLUS_J);
+ final Vector3D dGround = new Vector3D(1.0, iGround, -Vector3D.dotProduct(iGround, los), los).
+ add(Vector3D.crossProduct(los, jGround));
+
+ // get satellite dipole
+ // we don't use the basic yaw steering attitude model from ESA navipedia page
+ // but rely on the attitude that was computed by the propagator, which takes
+ // into account the proper noon and midnight turns for each satellite model
+ final Rotation satToInert = estimated.getStates()[0].toTransform().getRotation().revert();
+ final Vector3D iSat = satToInert.applyTo(Vector3D.PLUS_I);
+ final Vector3D jSat = satToInert.applyTo(Vector3D.PLUS_J);
+ final Vector3D dSat = new Vector3D(1.0, iSat, -Vector3D.dotProduct(iSat, los), los).
+ subtract(Vector3D.crossProduct(los, jSat));
+
+ // raw correction
+ final double correction = FastMath.copySign(Vector3D.angle(dSat, dGround),
+ Vector3D.dotProduct(los, Vector3D.crossProduct(dSat, dGround)));
+
+ // ensure continuity accross measurements
+ // we assume the various measurements are close enough in time
+ // (less the one satellite half-turn) so the angles remain close
+ angularWindUp = MathUtils.normalizeAngle(correction, angularWindUp);
+
+ // update estimate
+ estimated.setEstimatedValue(estimated.getEstimatedValue()[0] + angularWindUp / MathUtils.TWO_PI);
+
+ }
+
+}
diff --git a/src/main/java/org/orekit/estimation/measurements/gnss/WindUpFactory.java b/src/main/java/org/orekit/estimation/measurements/gnss/WindUpFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..a7e2889d4a79e87e21186fec4f01fb70f22e1def
--- /dev/null
+++ b/src/main/java/org/orekit/estimation/measurements/gnss/WindUpFactory.java
@@ -0,0 +1,80 @@
+/* Copyright 2002-2019 CS Systèmes d'Information
+ * Licensed to CS Systèmes d'Information (CS) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.estimation.measurements.gnss;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.orekit.gnss.SatelliteSystem;
+
+/** Factory for {@link WindUp wind-up} modifiers.
+ *
+ * The factory ensures the same instance is returned for all
+ * satellite/receiver pair, thus preserving phase continuity
+ * for successive measurements involving the same pair.
+ *
+ * @author Luc Maisonobe
+ * @since 10.1
+ */
+public class WindUpFactory {
+
+ /** Modifiers cache. */
+ private final Map>> modifiers;
+
+ /** Simple constructor.
+ */
+ WindUpFactory() {
+ this.modifiers = new HashMap<>();
+ }
+
+ /** Get a modifier for a satellite/receiver pair.
+ * @param system system the satellite belongs to
+ * @param prnNumber PRN number
+ * @param receiverName name of the receiver
+ * @return modifier for the satellite/receiver pair
+ */
+ public WindUp getWindUp(final SatelliteSystem system, final int prnNumber, final String receiverName) {
+
+ // select satellite system
+ Map> systemModifiers = modifiers.get(system);
+ if (systemModifiers == null) {
+ // build a new map for this satellite system
+ systemModifiers = new HashMap<>();
+ modifiers.put(system, systemModifiers);
+ }
+
+ // select satellite
+ Map satelliteModifiers = systemModifiers.get(prnNumber);
+ if (satelliteModifiers == null) {
+ // build a new map for this satellite
+ satelliteModifiers = new HashMap<>();
+ systemModifiers.put(prnNumber, satelliteModifiers);
+ }
+
+ // select receiver
+ WindUp receiverModifier = satelliteModifiers.get(receiverName);
+ if (receiverModifier == null) {
+ // build a new wind-up modifier
+ receiverModifier = new WindUp();
+ satelliteModifiers.put(receiverName, receiverModifier);
+ }
+
+ return receiverModifier;
+
+ }
+
+}
diff --git a/src/main/java/org/orekit/estimation/measurements/modifiers/DynamicOutlierFilter.java b/src/main/java/org/orekit/estimation/measurements/modifiers/DynamicOutlierFilter.java
index f43c68e0ba4d51e51e0c5e7c9d6013e331e2946d..17d1ea3fff2da1973742ec32f54166c3fb73d029 100644
--- a/src/main/java/org/orekit/estimation/measurements/modifiers/DynamicOutlierFilter.java
+++ b/src/main/java/org/orekit/estimation/measurements/modifiers/DynamicOutlierFilter.java
@@ -32,8 +32,7 @@ import org.orekit.estimation.measurements.ObservedMeasurement;
* @author Luc Maisonobe
* @since 9.2
*/
-public class DynamicOutlierFilter> extends OutlierFilter
-{
+public class DynamicOutlierFilter> extends OutlierFilter {
/** Current value of sigma. */
private double[] sigma;
diff --git a/src/main/java/org/orekit/estimation/measurements/modifiers/IonosphericDSConverter.java b/src/main/java/org/orekit/estimation/measurements/modifiers/IonosphericDSConverter.java
index dc4d853c5a8b74a42d9343a607d731a7a85844a5..d543bfdb769b0d2cc3520d3997c30db4ee5fe6d7 100644
--- a/src/main/java/org/orekit/estimation/measurements/modifiers/IonosphericDSConverter.java
+++ b/src/main/java/org/orekit/estimation/measurements/modifiers/IonosphericDSConverter.java
@@ -89,11 +89,10 @@ public class IonosphericDSConverter {
// mass never has derivatives
final DerivativeStructure dsM = factory.constant(state.getMass());
- final DerivativeStructure dsMu = factory.constant(state.getMu());
-
final FieldOrbit dsOrbit =
new FieldCartesianOrbit<>(new TimeStampedFieldPVCoordinates<>(state.getDate(), posDS, velDS, accDS),
- state.getFrame(), dsMu);
+ state.getFrame(),
+ factory.getDerivativeField().getZero().add(state.getMu()));
final FieldAttitude dsAttitude;
if (freeStateParameters > 3) {
@@ -149,7 +148,7 @@ public class IonosphericDSConverter {
extend(pv0.getPosition(), factory),
extend(pv0.getVelocity(), factory),
extend(pv0.getAcceleration(), factory)),
- s0.getFrame(), s0.getMu());
+ s0.getFrame(), extend(s0.getMu(), factory));
// attitude
final FieldAngularCoordinates ac0 = s0.getAttitude().getOrientation();
diff --git a/src/main/java/org/orekit/estimation/sequential/DSSTKalmanModel.java b/src/main/java/org/orekit/estimation/sequential/DSSTKalmanModel.java
index decd8d753cbfbbaa97a259be0673d589b9dae848..506420ab87f3d32c3c3105acf753ed4e2d719c6f 100644
--- a/src/main/java/org/orekit/estimation/sequential/DSSTKalmanModel.java
+++ b/src/main/java/org/orekit/estimation/sequential/DSSTKalmanModel.java
@@ -35,6 +35,7 @@ import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.estimation.measurements.EstimatedMeasurement;
import org.orekit.estimation.measurements.EstimationModifier;
+import org.orekit.estimation.measurements.ObservableSatellite;
import org.orekit.estimation.measurements.ObservedMeasurement;
import org.orekit.estimation.measurements.modifiers.DynamicOutlierFilter;
import org.orekit.orbits.Orbit;
@@ -931,7 +932,7 @@ public class DSSTKalmanModel implements KalmanODModel {
// so far. We use this to be able to apply the OutlierFilter modifiers on the predicted measurement.
predictedMeasurement = observedMeasurement.estimate(currentMeasurementNumber,
currentMeasurementNumber,
- predictedSpacecraftStates);
+ filterRelevant(observedMeasurement, predictedSpacecraftStates));
// Normalized measurement matrix (nxm)
final RealMatrix measurementMatrix = getMeasurementMatrix();
@@ -1008,13 +1009,28 @@ public class DSSTKalmanModel implements KalmanODModel {
// Compute the estimated measurement using estimated spacecraft state
correctedMeasurement = observedMeasurement.estimate(currentMeasurementNumber,
currentMeasurementNumber,
- correctedSpacecraftStates);
+ filterRelevant(observedMeasurement, correctedSpacecraftStates));
// Update the trajectory
// ---------------------
updateReferenceTrajectories(estimatedPropagators);
}
+ /** Filter relevant states for a measurement.
+ * @param observedMeasurement measurement to consider
+ * @param allStates all states
+ * @return array containing only the states relevant to the measurement
+ * @since 10.1
+ */
+ private SpacecraftState[] filterRelevant(final ObservedMeasurement> observedMeasurement, final SpacecraftState[] allStates) {
+ final List satellites = observedMeasurement.getSatellites();
+ final SpacecraftState[] relevantStates = new SpacecraftState[satellites.size()];
+ for (int i = 0; i < relevantStates.length; ++i) {
+ relevantStates[i] = allStates[satellites.get(i).getPropagatorIndex()];
+ }
+ return relevantStates;
+ }
+
/** Set the predicted normalized state vector.
* The predicted/propagated orbit is used to update the state vector
* @param date prediction date
diff --git a/src/main/java/org/orekit/estimation/sequential/KalmanModel.java b/src/main/java/org/orekit/estimation/sequential/KalmanModel.java
index 93ec68ab54c42ac43b490f1a0e8863d11c2c333e..c9c0a011e8da431b8753cc21d665c2bd27cbde79 100644
--- a/src/main/java/org/orekit/estimation/sequential/KalmanModel.java
+++ b/src/main/java/org/orekit/estimation/sequential/KalmanModel.java
@@ -35,6 +35,7 @@ import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.estimation.measurements.EstimatedMeasurement;
import org.orekit.estimation.measurements.EstimationModifier;
+import org.orekit.estimation.measurements.ObservableSatellite;
import org.orekit.estimation.measurements.ObservedMeasurement;
import org.orekit.estimation.measurements.modifiers.DynamicOutlierFilter;
import org.orekit.orbits.Orbit;
@@ -909,7 +910,7 @@ public class KalmanModel implements KalmanODModel {
// so far. We use this to be able to apply the OutlierFilter modifiers on the predicted measurement.
predictedMeasurement = observedMeasurement.estimate(currentMeasurementNumber,
currentMeasurementNumber,
- predictedSpacecraftStates);
+ filterRelevant(observedMeasurement, predictedSpacecraftStates));
// Normalized measurement matrix (nxm)
final RealMatrix measurementMatrix = getMeasurementMatrix();
@@ -986,13 +987,28 @@ public class KalmanModel implements KalmanODModel {
// Compute the estimated measurement using estimated spacecraft state
correctedMeasurement = observedMeasurement.estimate(currentMeasurementNumber,
currentMeasurementNumber,
- correctedSpacecraftStates);
+ filterRelevant(observedMeasurement, correctedSpacecraftStates));
// Update the trajectory
// ---------------------
updateReferenceTrajectories(estimatedPropagators);
}
+ /** Filter relevant states for a measurement.
+ * @param observedMeasurement measurement to consider
+ * @param allStates all states
+ * @return array containing only the states relevant to the measurement
+ * @since 10.1
+ */
+ private SpacecraftState[] filterRelevant(final ObservedMeasurement> observedMeasurement, final SpacecraftState[] allStates) {
+ final List satellites = observedMeasurement.getSatellites();
+ final SpacecraftState[] relevantStates = new SpacecraftState[satellites.size()];
+ for (int i = 0; i < relevantStates.length; ++i) {
+ relevantStates[i] = allStates[satellites.get(i).getPropagatorIndex()];
+ }
+ return relevantStates;
+ }
+
/** Set the predicted normalized state vector.
* The predicted/propagated orbit is used to update the state vector
* @param date prediction date
diff --git a/src/main/java/org/orekit/files/ccsds/CcsdsModifiedFrame.java b/src/main/java/org/orekit/files/ccsds/CcsdsModifiedFrame.java
index 5f8d5b828bc6f86e17d451379e6cd3537844c08f..dde964f91fad98aaba98128f45ad4248fe506f54 100644
--- a/src/main/java/org/orekit/files/ccsds/CcsdsModifiedFrame.java
+++ b/src/main/java/org/orekit/files/ccsds/CcsdsModifiedFrame.java
@@ -110,7 +110,7 @@ public class CcsdsModifiedFrame extends Frame {
@Override
public Transform getTransform(final AbsoluteDate date) {
- return new Transform(date, body.getPVCoordinates(date, frame));
+ return new Transform(date, body.getPVCoordinates(date, frame).negate());
}
@Override
@@ -118,7 +118,7 @@ public class CcsdsModifiedFrame extends Frame {
final FieldAbsoluteDate date) {
return new FieldTransform<>(
date,
- body.getPVCoordinates(date, frame));
+ body.getPVCoordinates(date, frame).negate());
}
}
diff --git a/src/main/java/org/orekit/files/ccsds/OEMParser.java b/src/main/java/org/orekit/files/ccsds/OEMParser.java
index 9ac51ea1099578cda85d41f72990ad7b5ed93286..b5b6eca31eb4db72650ba5835348824f4da83889 100644
--- a/src/main/java/org/orekit/files/ccsds/OEMParser.java
+++ b/src/main/java/org/orekit/files/ccsds/OEMParser.java
@@ -20,6 +20,7 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
@@ -138,7 +139,7 @@ public class OEMParser extends ODMParser implements EphemerisFileParser {
/** {@inheritDoc} */
public OEMFile parse(final InputStream stream, final String fileName) {
- try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"))) {
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) {
return parse(reader, fileName);
} catch (IOException ioe) {
throw new OrekitException(ioe, new DummyLocalizable(ioe.getMessage()));
diff --git a/src/main/java/org/orekit/files/ccsds/OEMWriter.java b/src/main/java/org/orekit/files/ccsds/OEMWriter.java
index 71fa0048e201bd194687fd417b9935d9c73c1ae7..53bb84e29aaab60a1382ad5d3c3b172056df0472 100644
--- a/src/main/java/org/orekit/files/ccsds/OEMWriter.java
+++ b/src/main/java/org/orekit/files/ccsds/OEMWriter.java
@@ -23,6 +23,8 @@ import java.util.Map;
import org.orekit.errors.OrekitIllegalArgumentException;
import org.orekit.errors.OrekitMessages;
+import org.orekit.files.ccsds.OEMFile.CovarianceMatrix;
+import org.orekit.files.ccsds.OEMFile.EphemeridesBlock;
import org.orekit.files.ccsds.StreamingOemWriter.Segment;
import org.orekit.files.general.EphemerisFile;
import org.orekit.files.general.EphemerisFile.EphemerisSegment;
@@ -163,11 +165,20 @@ public class OEMWriter implements EphemerisFileWriter {
metadata.put(Keyword.STOP_TIME, segment.getStop().toString(timeScale));
metadata.put(Keyword.INTERPOLATION_DEGREE,
String.valueOf(segment.getInterpolationSamples() - 1));
+
final Segment segmentWriter = oemWriter.newSegment(null, metadata);
segmentWriter.writeMetadata();
for (final TimeStampedPVCoordinates coordinates : segment.getCoordinates()) {
segmentWriter.writeEphemerisLine(coordinates);
}
+
+ if (segment instanceof EphemeridesBlock) {
+ final EphemeridesBlock curr_ephem_block = (EphemeridesBlock) segment;
+ final List covarianceMatrices = curr_ephem_block.getCovarianceMatrices();
+ if (!covarianceMatrices.isEmpty()) {
+ segmentWriter.writeCovarianceMatrices(covarianceMatrices);
+ }
+ }
}
}
diff --git a/src/main/java/org/orekit/files/ccsds/OMMParser.java b/src/main/java/org/orekit/files/ccsds/OMMParser.java
index d3dd6fa5a1c35009da9b99159c984bfb9663cc1f..c195c1b0ca1f3560cc01f57e484f7e0d635fbc04 100644
--- a/src/main/java/org/orekit/files/ccsds/OMMParser.java
+++ b/src/main/java/org/orekit/files/ccsds/OMMParser.java
@@ -20,6 +20,7 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
@@ -139,7 +140,7 @@ public class OMMParser extends ODMParser {
try {
final BufferedReader reader =
- new BufferedReader(new InputStreamReader(stream, "UTF-8"));
+ new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8));
// initialize internal data structures
final ParseInfo pi = new ParseInfo();
diff --git a/src/main/java/org/orekit/files/ccsds/OPMParser.java b/src/main/java/org/orekit/files/ccsds/OPMParser.java
index 24632228bb9944715a7db3e88a1eba1d2426de4e..ba96adf6e98a24b9482125b5adeed9992323be2d 100644
--- a/src/main/java/org/orekit/files/ccsds/OPMParser.java
+++ b/src/main/java/org/orekit/files/ccsds/OPMParser.java
@@ -20,6 +20,7 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
@@ -132,7 +133,7 @@ public class OPMParser extends ODMParser {
public OPMFile parse(final InputStream stream, final String fileName) {
try {
- final BufferedReader reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
+ final BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8));
// initialize internal data structures
final ParseInfo pi = new ParseInfo();
pi.fileName = fileName;
diff --git a/src/main/java/org/orekit/files/ccsds/StreamingOemWriter.java b/src/main/java/org/orekit/files/ccsds/StreamingOemWriter.java
index 8709523cc4dd02d3531789c9eebb08a762b9cac2..79c4614512eee5620cd307c4f2c18e68a34bbdcc 100644
--- a/src/main/java/org/orekit/files/ccsds/StreamingOemWriter.java
+++ b/src/main/java/org/orekit/files/ccsds/StreamingOemWriter.java
@@ -20,16 +20,22 @@ import java.io.IOException;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Arrays;
+import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
+import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.hipparchus.exception.LocalizedCoreFormats;
+import org.hipparchus.linear.RealMatrix;
import org.orekit.bodies.CelestialBodyFactory;
import org.orekit.errors.OrekitException;
+import org.orekit.errors.OrekitMessages;
+import org.orekit.files.ccsds.OEMFile.CovarianceMatrix;
import org.orekit.frames.FactoryManagedFrame;
import org.orekit.frames.Frame;
+import org.orekit.frames.LOFType;
import org.orekit.frames.Predefined;
import org.orekit.frames.VersionedITRF;
import org.orekit.propagation.Propagator;
@@ -495,6 +501,46 @@ public class StreamingOemWriter {
writer.append(NEW_LINE);
}
+ /**
+ * Write covariance matrices of the segment according to section 5.2.5.
+ *
+ * @param covarianceMatrices the list of covariance matrices related to the segment.
+ * @throws IOException if the output stream throws one while writing.
+ */
+ public void writeCovarianceMatrices(final List covarianceMatrices)
+ throws IOException {
+ writer.append("COVARIANCE_START").append(NEW_LINE);
+ // Sort to ensure having the matrices in chronological order when
+ // they are in the same data section (see section 5.2.5.7)
+ Collections.sort(covarianceMatrices, (mat1, mat2)->mat1.getEpoch().compareTo(mat2.getEpoch()));
+ for (final CovarianceMatrix covarianceMatrix : covarianceMatrices) {
+ final String epoch = dateToString(covarianceMatrix.getEpoch().getComponents(timeScale));
+ writeKeyValue(Keyword.EPOCH, epoch);
+
+ if (covarianceMatrix.getFrame() != null ) {
+ writeKeyValue(Keyword.COV_REF_FRAME, guessFrame(covarianceMatrix.getFrame()));
+ } else if (covarianceMatrix.getLofType() != null) {
+ if (covarianceMatrix.getLofType() == LOFType.QSW) {
+ writeKeyValue(Keyword.COV_REF_FRAME, "RTN");
+ } else if (covarianceMatrix.getLofType() == LOFType.TNW) {
+ writeKeyValue(Keyword.COV_REF_FRAME, covarianceMatrix.getLofType().name());
+ } else {
+ throw new OrekitException(OrekitMessages.CCSDS_INVALID_FRAME, toString());
+ }
+ }
+
+ final RealMatrix covRealMatrix = covarianceMatrix.getMatrix();
+ for (int i = 0; i < covRealMatrix.getRowDimension(); i++) {
+ writer.append(Double.toString(covRealMatrix.getEntry(i, 0)));
+ for (int j = 1; j < i + 1; j++) {
+ writer.append(" ").append(Double.toString(covRealMatrix.getEntry(i, j)));
+ }
+ writer.append(NEW_LINE);
+ }
+ }
+ writer.append("COVARIANCE_STOP").append(NEW_LINE).append(NEW_LINE);
+ }
+
/**
* {@inheritDoc}
*
diff --git a/src/main/java/org/orekit/files/ccsds/TDMParser.java b/src/main/java/org/orekit/files/ccsds/TDMParser.java
index 4effd3901a7de59ab627be009708d7ebdafd74f0..ac235760b297bbbcaf1b565ccc27ce387dad4641 100644
--- a/src/main/java/org/orekit/files/ccsds/TDMParser.java
+++ b/src/main/java/org/orekit/files/ccsds/TDMParser.java
@@ -21,6 +21,7 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@@ -643,7 +644,7 @@ public class TDMParser extends DefaultHandler {
* @return parsed file content in a TDMFile object
*/
public TDMFile parse(final InputStream stream, final String fileName) {
- try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"))) {
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) {
try {
// Initialize internal TDMFile
final TDMFile tdmFile = parseInfo.tdmFile;
diff --git a/src/main/java/org/orekit/forces/gravity/potential/AstronomicalAmplitudeReader.java b/src/main/java/org/orekit/forces/gravity/potential/AstronomicalAmplitudeReader.java
index 101ede303ac63aea613cee480989eb7684b07fa8..27ed1505deefe76a469cda5c017bdc77b7982103 100644
--- a/src/main/java/org/orekit/forces/gravity/potential/AstronomicalAmplitudeReader.java
+++ b/src/main/java/org/orekit/forces/gravity/potential/AstronomicalAmplitudeReader.java
@@ -20,6 +20,7 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -122,7 +123,7 @@ public class AstronomicalAmplitudeReader implements DataLoader {
throws IOException {
// parse the file
- final BufferedReader r = new BufferedReader(new InputStreamReader(input, "UTF-8"));
+ final BufferedReader r = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
int lineNumber = 0;
for (String line = r.readLine(); line != null; line = r.readLine()) {
++lineNumber;
diff --git a/src/main/java/org/orekit/forces/gravity/potential/EGMFormatReader.java b/src/main/java/org/orekit/forces/gravity/potential/EGMFormatReader.java
index ba48deba478a4bf8cabb17ef62df70ef52bd945b..92e2662ae9335a706a0d14e6ea65273e6ecc9d8a 100644
--- a/src/main/java/org/orekit/forces/gravity/potential/EGMFormatReader.java
+++ b/src/main/java/org/orekit/forces/gravity/potential/EGMFormatReader.java
@@ -20,6 +20,7 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
@@ -94,7 +95,7 @@ public class EGMFormatReader extends PotentialCoefficientsReader {
setTideSystem(TideSystem.TIDE_FREE);
}
- final BufferedReader r = new BufferedReader(new InputStreamReader(input, "UTF-8"));
+ final BufferedReader r = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
final List> c = new ArrayList>();
final List> s = new ArrayList>();
boolean okFields = true;
diff --git a/src/main/java/org/orekit/forces/gravity/potential/FESCHatEpsilonReader.java b/src/main/java/org/orekit/forces/gravity/potential/FESCHatEpsilonReader.java
index e457ac41b790b29f1e03b0861292d93fa90b226d..44d44e597f08e21e8d41caa55393f3d0c482f3df 100644
--- a/src/main/java/org/orekit/forces/gravity/potential/FESCHatEpsilonReader.java
+++ b/src/main/java/org/orekit/forces/gravity/potential/FESCHatEpsilonReader.java
@@ -20,6 +20,7 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -128,7 +129,7 @@ public class FESCHatEpsilonReader extends OceanTidesReader {
// parse the file
startParse(name);
- final BufferedReader r = new BufferedReader(new InputStreamReader(input, "UTF-8"));
+ final BufferedReader r = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
int lineNumber = 0;
for (String line = r.readLine(); line != null; line = r.readLine()) {
++lineNumber;
diff --git a/src/main/java/org/orekit/forces/gravity/potential/FESCnmSnmReader.java b/src/main/java/org/orekit/forces/gravity/potential/FESCnmSnmReader.java
index 0cc1286fcf16e4f09d27083bcaa37ceb8185598a..7772e8fd00124c12b7e00de4baa4ee1d589490a0 100644
--- a/src/main/java/org/orekit/forces/gravity/potential/FESCnmSnmReader.java
+++ b/src/main/java/org/orekit/forces/gravity/potential/FESCnmSnmReader.java
@@ -20,6 +20,7 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -92,7 +93,7 @@ public class FESCnmSnmReader extends OceanTidesReader {
// parse the file
startParse(name);
- final BufferedReader r = new BufferedReader(new InputStreamReader(input, "UTF-8"));
+ final BufferedReader r = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
int lineNumber = 0;
boolean dataStarted = false;
for (String line = r.readLine(); line != null; line = r.readLine()) {
diff --git a/src/main/java/org/orekit/forces/gravity/potential/GRGSFormatReader.java b/src/main/java/org/orekit/forces/gravity/potential/GRGSFormatReader.java
index 4fd4d519acf32bf876bbaeb29d4ec76fab08e069..5e8e37ecb22be34bcb78209a728a4daf5418a37d 100644
--- a/src/main/java/org/orekit/forces/gravity/potential/GRGSFormatReader.java
+++ b/src/main/java/org/orekit/forces/gravity/potential/GRGSFormatReader.java
@@ -20,6 +20,7 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
@@ -121,7 +122,7 @@ public class GRGSFormatReader extends PotentialCoefficientsReader {
// 0 0 .99999999988600E+00 .00000000000000E+00 .153900E-09 .000000E+00
// 2 0 -0.48416511550920E-03 0.00000000000000E+00 .204904E-10 .000000E+00
- final BufferedReader r = new BufferedReader(new InputStreamReader(input, "UTF-8"));
+ final BufferedReader r = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
int lineNumber = 0;
double[][] c = null;
double[][] s = null;
diff --git a/src/main/java/org/orekit/forces/gravity/potential/ICGEMFormatReader.java b/src/main/java/org/orekit/forces/gravity/potential/ICGEMFormatReader.java
index 630226b21f790fb39154ccec94f509c5253f17d8..75a64c35536f9ec3be089a9380c8c06f049ff116 100644
--- a/src/main/java/org/orekit/forces/gravity/potential/ICGEMFormatReader.java
+++ b/src/main/java/org/orekit/forces/gravity/potential/ICGEMFormatReader.java
@@ -20,6 +20,7 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
@@ -202,7 +203,7 @@ public class ICGEMFormatReader extends PotentialCoefficientsReader {
normalized = true;
tideSystem = TideSystem.UNKNOWN;
- final BufferedReader r = new BufferedReader(new InputStreamReader(input, "UTF-8"));
+ final BufferedReader r = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
boolean inHeader = true;
double[][] c = null;
double[][] s = null;
diff --git a/src/main/java/org/orekit/forces/gravity/potential/SHMFormatReader.java b/src/main/java/org/orekit/forces/gravity/potential/SHMFormatReader.java
index f102b959504d9dfea315cc805ee46ed88e312240..bf7d6fd3e2ed1ed35bdfac8318da89c4cb57030f 100644
--- a/src/main/java/org/orekit/forces/gravity/potential/SHMFormatReader.java
+++ b/src/main/java/org/orekit/forces/gravity/potential/SHMFormatReader.java
@@ -20,6 +20,7 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
@@ -90,7 +91,7 @@ public class SHMFormatReader extends PotentialCoefficientsReader {
boolean normalized = false;
TideSystem tideSystem = TideSystem.UNKNOWN;
- final BufferedReader r = new BufferedReader(new InputStreamReader(input, "UTF-8"));
+ final BufferedReader r = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
boolean okEarth = false;
boolean okSHM = false;
boolean okCoeffs = false;
diff --git a/src/main/java/org/orekit/frames/BulletinAFilesLoader.java b/src/main/java/org/orekit/frames/BulletinAFilesLoader.java
index 7d3f250af972d2155ba0f05b429ad163835a7289..102876fa04e86490c276f8e01e0be56d02b7b96b 100644
--- a/src/main/java/org/orekit/frames/BulletinAFilesLoader.java
+++ b/src/main/java/org/orekit/frames/BulletinAFilesLoader.java
@@ -20,6 +20,7 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -422,7 +423,7 @@ class BulletinAFilesLoader implements EOPHistoryLoader {
this.fileName = name;
// set up a reader for line-oriented bulletin A files
- final BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
+ final BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
lineNumber = 0;
firstMJD = -1;
diff --git a/src/main/java/org/orekit/frames/BulletinBFilesLoader.java b/src/main/java/org/orekit/frames/BulletinBFilesLoader.java
index b8e5e74797f441d573f223e359c3b04e5dcca123..1a48a80b340b382a8240c2098d64c7c4f2649ce3 100644
--- a/src/main/java/org/orekit/frames/BulletinBFilesLoader.java
+++ b/src/main/java/org/orekit/frames/BulletinBFilesLoader.java
@@ -20,6 +20,7 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -293,7 +294,7 @@ class BulletinBFilesLoader implements EOPHistoryLoader {
configuration = null;
// set up a reader for line-oriented bulletin B files
- final BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
+ final BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
// reset parse info to start new file (do not clear history!)
fieldsMap.clear();
diff --git a/src/main/java/org/orekit/frames/EOPC04FilesLoader.java b/src/main/java/org/orekit/frames/EOPC04FilesLoader.java
index 481d14fa69f5a1b1cd75cafdff2e3f7f446aa6b5..a59ddae5feb4ebfdbbaa290618783f17515b1375 100644
--- a/src/main/java/org/orekit/frames/EOPC04FilesLoader.java
+++ b/src/main/java/org/orekit/frames/EOPC04FilesLoader.java
@@ -20,6 +20,7 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.SortedSet;
@@ -206,7 +207,7 @@ class EOPC04FilesLoader implements EOPHistoryLoader {
ITRFVersionLoader.ITRFVersionConfiguration configuration = null;
// set up a reader for line-oriented bulletin B files
- final BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
+ final BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
// reset parse info to start new file (do not clear history!)
lineNumber = 0;
diff --git a/src/main/java/org/orekit/frames/ITRFVersionLoader.java b/src/main/java/org/orekit/frames/ITRFVersionLoader.java
index 26eaa53a12282ef3079c2bf43cff04f96bc7aed5..fe622d8dd221fc317a9d3f218dbe737ddd2ec5a6 100644
--- a/src/main/java/org/orekit/frames/ITRFVersionLoader.java
+++ b/src/main/java/org/orekit/frames/ITRFVersionLoader.java
@@ -20,6 +20,7 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
@@ -146,7 +147,7 @@ public class ITRFVersionLoader {
final Pattern patternDD = Pattern.compile(START + NON_BLANK_FIELD + CALENDAR_DATE + CALENDAR_DATE + ITRF + END);
// set up a reader for line-oriented bulletin A files
- final BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
+ final BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
int lineNumber = 0;
String line = null;
diff --git a/src/main/java/org/orekit/frames/RapidDataAndPredictionColumnsLoader.java b/src/main/java/org/orekit/frames/RapidDataAndPredictionColumnsLoader.java
index fec515f471461b1c927d47bced54f5f9d02d6a21..36c91dbf6a7eb667631b11c76d076b581751d2f3 100644
--- a/src/main/java/org/orekit/frames/RapidDataAndPredictionColumnsLoader.java
+++ b/src/main/java/org/orekit/frames/RapidDataAndPredictionColumnsLoader.java
@@ -20,6 +20,7 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.SortedSet;
@@ -201,7 +202,7 @@ class RapidDataAndPredictionColumnsLoader implements EOPHistoryLoader {
ITRFVersionLoader.ITRFVersionConfiguration configuration = null;
// set up a reader for line-oriented bulletin B files
- final BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
+ final BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
// reset parse info to start new file (do not clear history!)
lineNumber = 0;
diff --git a/src/main/java/org/orekit/frames/RapidDataAndPredictionXMLLoader.java b/src/main/java/org/orekit/frames/RapidDataAndPredictionXMLLoader.java
index 579e82b471f809abde704154af6df8ad6032fcdf..15e8abd69816c5f137bb044a8b65447e7a2957cc 100644
--- a/src/main/java/org/orekit/frames/RapidDataAndPredictionXMLLoader.java
+++ b/src/main/java/org/orekit/frames/RapidDataAndPredictionXMLLoader.java
@@ -19,6 +19,7 @@ package org.orekit.frames;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.SortedSet;
@@ -121,7 +122,7 @@ class RapidDataAndPredictionXMLLoader implements EOPHistoryLoader {
reader.setEntityResolver((publicId, systemId) -> new InputSource());
// read all file, ignoring header
- reader.parse(new InputSource(new InputStreamReader(input, "UTF-8")));
+ reader.parse(new InputSource(new InputStreamReader(input, StandardCharsets.UTF_8)));
} catch (SAXException se) {
if ((se.getCause() != null) && (se.getCause() instanceof OrekitException)) {
diff --git a/src/main/java/org/orekit/geometry/fov/AbstractFieldOfView.java b/src/main/java/org/orekit/geometry/fov/AbstractFieldOfView.java
new file mode 100644
index 0000000000000000000000000000000000000000..6fb16775bf6de838e6e640f5740face5394f8a4a
--- /dev/null
+++ b/src/main/java/org/orekit/geometry/fov/AbstractFieldOfView.java
@@ -0,0 +1,44 @@
+/* Copyright 2002-2019 CS Systèmes d'Information
+ * Licensed to CS Systèmes d'Information (CS) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.geometry.fov;
+
+/** Abstract class representing a spacecraft sensor Field Of View.
+ * @author Luc Maisonobe
+ * @since 10.1
+ */
+public abstract class AbstractFieldOfView implements FieldOfView {
+
+ /** Margin to apply to the zone. */
+ private final double margin;
+
+ /** Build a new instance.
+ * @param margin angular margin to apply to the zone (if positive,
+ * points outside of the raw FoV but close enough to the boundary are
+ * considered visible; if negative, points inside of the raw FoV
+ * but close enough to the boundary are considered not visible)
+ */
+ protected AbstractFieldOfView(final double margin) {
+ this.margin = margin;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public double getMargin() {
+ return margin;
+ }
+
+}
diff --git a/src/main/java/org/orekit/geometry/fov/CircularFieldOfView.java b/src/main/java/org/orekit/geometry/fov/CircularFieldOfView.java
new file mode 100644
index 0000000000000000000000000000000000000000..72702692eeef5ef50a7089e615b0038adbc03fd8
--- /dev/null
+++ b/src/main/java/org/orekit/geometry/fov/CircularFieldOfView.java
@@ -0,0 +1,93 @@
+/* Copyright 2002-2019 CS Systèmes d'Information
+ * Licensed to CS Systèmes d'Information (CS) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.geometry.fov;
+
+import org.hipparchus.geometry.euclidean.threed.Vector3D;
+import org.hipparchus.util.FastMath;
+import org.hipparchus.util.SinCos;
+import org.orekit.propagation.events.VisibilityTrigger;
+
+/** Class representing a spacecraft sensor Field Of View with circular shape.
+ * The field of view is defined by an axis and an half-aperture angle.
+ * @author Luc Maisonobe
+ * @since 10.1
+ */
+public class CircularFieldOfView extends SmoothFieldOfView {
+
+ /** FOV half aperture angle. */
+ private final double halfAperture;
+
+ /** Scaled X axis defining FoV boundary. */
+ private final Vector3D scaledX;
+
+ /** Scaled Y axis defining FoV boundary. */
+ private final Vector3D scaledY;
+
+ /** Scaled Z axis defining FoV boundary. */
+ private final Vector3D scaledZ;
+
+ /** Build a new instance.
+ * @param center direction of the FOV center, in spacecraft frame
+ * @param halfAperture FOV half aperture angle
+ * @param margin angular margin to apply to the zone (if positive,
+ * the Field Of View will consider points slightly outside of the
+ * zone are still visible)
+ */
+ public CircularFieldOfView(final Vector3D center, final double halfAperture,
+ final double margin) {
+
+ super(center, center.orthogonal(), margin);
+ this.halfAperture = halfAperture;
+
+ // precompute utility vectors for walking around the FoV
+ final SinCos sc = FastMath.sinCos(halfAperture);
+ scaledX = new Vector3D(sc.sin(), getX());
+ scaledY = new Vector3D(sc.sin(), getY());
+ scaledZ = new Vector3D(sc.cos(), getZ());
+
+ }
+
+ /** get the FOV half aperture angle.
+ * @return FOV half aperture angle
+ */
+ public double getHalfAperture() {
+ return halfAperture;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public double offsetFromBoundary(final Vector3D lineOfSight, final double angularRadius,
+ final VisibilityTrigger trigger) {
+ return Vector3D.angle(getCenter(), lineOfSight) - halfAperture +
+ trigger.radiusCorrection(angularRadius) - getMargin();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Vector3D projectToBoundary(final Vector3D lineOfSight) {
+ return directionAt(FastMath.atan2(Vector3D.dotProduct(lineOfSight, getY()),
+ Vector3D.dotProduct(lineOfSight, getX())));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected Vector3D directionAt(final double angle) {
+ final SinCos sc = FastMath.sinCos(angle);
+ return new Vector3D(sc.cos(), scaledX, sc.sin(), scaledY, 1.0, scaledZ);
+ }
+
+}
diff --git a/src/main/java/org/orekit/geometry/fov/DoubleDihedraFieldOfView.java b/src/main/java/org/orekit/geometry/fov/DoubleDihedraFieldOfView.java
new file mode 100644
index 0000000000000000000000000000000000000000..2fbce4ad17bbc5a18e24f93f32bd6bffc0ab2753
--- /dev/null
+++ b/src/main/java/org/orekit/geometry/fov/DoubleDihedraFieldOfView.java
@@ -0,0 +1,109 @@
+/* Copyright 2002-2019 CS Systèmes d'Information
+ * Licensed to CS Systèmes d'Information (CS) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.geometry.fov;
+
+import org.hipparchus.exception.LocalizedCoreFormats;
+import org.hipparchus.geometry.euclidean.threed.Rotation;
+import org.hipparchus.geometry.euclidean.threed.RotationConvention;
+import org.hipparchus.geometry.euclidean.threed.Vector3D;
+import org.hipparchus.geometry.partitioning.Region;
+import org.hipparchus.geometry.partitioning.RegionFactory;
+import org.hipparchus.geometry.spherical.twod.Sphere2D;
+import org.hipparchus.geometry.spherical.twod.SphericalPolygonsSet;
+import org.hipparchus.util.FastMath;
+import org.orekit.errors.OrekitException;
+
+/** Class representing a spacecraft sensor Field Of View with dihedral shape (i.e. rectangular shape).
+ * @author Luc Maisonobe
+ * @since 10.1
+ */
+public class DoubleDihedraFieldOfView extends PolygonalFieldOfView {
+
+ /** Build a Field Of View with dihedral shape (i.e. rectangular shape).
+ * @param center Direction of the FOV center, in spacecraft frame
+ * @param axis1 FOV dihedral axis 1, in spacecraft frame
+ * @param halfAperture1 FOV dihedral half aperture angle 1,
+ * must be less than π/2, i.e. full dihedra must be smaller then
+ * an hemisphere
+ * @param axis2 FOV dihedral axis 2, in spacecraft frame
+ * @param halfAperture2 FOV dihedral half aperture angle 2,
+ * must be less than π/2, i.e. full dihedra must be smaller then
+ * an hemisphere
+ * @param margin angular margin to apply to the zone (if positive,
+ * points outside of the raw FoV but close enough to the boundary are
+ * considered visible; if negative, points inside of the raw FoV
+ * but close enough to the boundary are considered not visible)
+ */
+ public DoubleDihedraFieldOfView(final Vector3D center,
+ final Vector3D axis1, final double halfAperture1,
+ final Vector3D axis2, final double halfAperture2,
+ final double margin) {
+ super(createPolygon(center, axis1, halfAperture1, axis2, halfAperture2), margin);
+ }
+
+ /** Create polygon.
+ * @param center Direction of the FOV center, in spacecraft frame
+ * @param axis1 FOV dihedral axis 1, in spacecraft frame
+ * @param halfAperture1 FOV dihedral half aperture angle 1,
+ * must be less than π/2, i.e. full dihedra must be smaller then
+ * an hemisphere
+ * @param axis2 FOV dihedral axis 2, in spacecraft frame
+ * @param halfAperture2 FOV dihedral half aperture angle 2,
+ * must be less than π/2, i.e. full dihedra must be smaller then
+ * an hemisphere
+ * @return built polygon
+ */
+ private static SphericalPolygonsSet createPolygon(final Vector3D center,
+ final Vector3D axis1, final double halfAperture1,
+ final Vector3D axis2, final double halfAperture2) {
+ final RegionFactory factory = new RegionFactory();
+ final double tolerance = FastMath.max(FastMath.ulp(2.0 * FastMath.PI),
+ 1.0e-12 * FastMath.max(halfAperture1, halfAperture2));
+ final Region dihedra1 = buildDihedra(factory, tolerance, center, axis1, halfAperture1);
+ final Region dihedra2 = buildDihedra(factory, tolerance, center, axis2, halfAperture2);
+ return (SphericalPolygonsSet) factory.intersection(dihedra1, dihedra2);
+ }
+
+ /** Build a dihedra.
+ * @param factory factory for regions
+ * @param tolerance tolerance below which points are considered equal
+ * @param center Direction of the FOV center, in spacecraft frame
+ * @param axis FOV dihedral axis, in spacecraft frame
+ * @param halfAperture FOV dihedral half aperture angle,
+ * must be less than π/2, i.e. full dihedra must be smaller then
+ * an hemisphere
+ * @return dihedra
+ */
+ private static Region buildDihedra(final RegionFactory factory,
+ final double tolerance, final Vector3D center,
+ final Vector3D axis, final double halfAperture) {
+ if (halfAperture > 0.5 * FastMath.PI) {
+ throw new OrekitException(LocalizedCoreFormats.OUT_OF_RANGE_SIMPLE,
+ halfAperture, 0.0, 0.5 * FastMath.PI);
+ }
+
+ final Rotation r = new Rotation(axis, halfAperture, RotationConvention.VECTOR_OPERATOR);
+ final Vector3D normalCenterPlane = Vector3D.crossProduct(axis, center);
+ final Vector3D normalSidePlus = r.applyInverseTo(normalCenterPlane);
+ final Vector3D normalSideMinus = r.applyTo(normalCenterPlane.negate());
+
+ return factory.intersection(new SphericalPolygonsSet(normalSidePlus, tolerance),
+ new SphericalPolygonsSet(normalSideMinus, tolerance));
+
+ }
+
+}
diff --git a/src/main/java/org/orekit/geometry/fov/EllipticalFieldOfView.java b/src/main/java/org/orekit/geometry/fov/EllipticalFieldOfView.java
new file mode 100644
index 0000000000000000000000000000000000000000..ec28282d4a60bb942653d663d43e14ab4b9d206a
--- /dev/null
+++ b/src/main/java/org/orekit/geometry/fov/EllipticalFieldOfView.java
@@ -0,0 +1,409 @@
+/* Copyright 2002-2019 CS Systèmes d'Information
+ * Licensed to CS Systèmes d'Information (CS) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.geometry.fov;
+
+import org.hipparchus.RealFieldElement;
+import org.hipparchus.analysis.differentiation.DSFactory;
+import org.hipparchus.analysis.differentiation.DerivativeStructure;
+import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
+import org.hipparchus.geometry.euclidean.threed.Vector3D;
+import org.hipparchus.util.FastMath;
+import org.hipparchus.util.SinCos;
+import org.orekit.propagation.events.VisibilityTrigger;
+
+/** Class representing a spacecraft sensor Field Of View with elliptical shape.
+ *
+ * There are several ways to define an elliptical shape on the unit sphere.
+ *
+ *
+ * Without loss of generality, one can assume that with a suitable rotation
+ * the ellipse center is along the Zell axis and the ellipse principal axes
+ * are along the Xell and Yell axes. The first defining
+ * elements for an ellipse are these canonical axes. This class allows specifying
+ * them by giving directly the Zell axis as the {@code center} of
+ * the ellipse, and giving a {@code primaryMeridian} vector in the (+Xell,
+ * Zell) half-plane. It is allowed to have {@code primaryMeridian} not
+ * orthogonal to {@code center} as orthogonality will be fixed internally.
+ *
+ *
+ * We can define angular coordinates \((\alpha, \beta)\) as dihedra angles around the
+ * +Yell and -Xell axes respectively to specify points on the
+ * unit sphere. The corresponding Cartesian coordinates will be
+ * \[P_{\alpha,\beta}\left(\begin{gather*}
+ * \frac{\sin\alpha\cos\beta}{\sqrt{1-\sin^2\alpha\sin^2\beta}}\\
+ * \frac{\cos\alpha\sin\beta}{\sqrt{1-\sin^2\alpha\sin^2\beta}}\\
+ * \frac{\cos\alpha\cos\beta}{\sqrt{1-\sin^2\alpha\sin^2\beta}}
+ * \end{gather*}\right)\]
+ * which shows that angle \(\beta=0\) corresponds to the (Xell, Zell)
+ * plane and that angle \(\alpha=0\) corresponds to the (Yell, Zell)
+ * plane. Note that at least one of the angles must be different from \(\pm\frac{\pi}{2}\),
+ * which means that the expression above is singular for points in the (Xell,
+ * Yell) plane.
+ *
+ *
+ * The size of the ellipse is defined by its half aperture angles \(\lambda\) along the
+ * Xell axis and \(\mu\) along the Yell axis.
+ * For points belonging to the ellipse, we always have \(-\lambda \le \alpha \le +\lambda\)
+ * and \(-\mu \le \beta \le +\mu\), equalities being reached at the end of principal axes.
+ * An ellipse defined on the sphere is not a planar ellipse because the four endpoints
+ * \((\alpha=\pm\lambda, \beta=0)\) and \((\alpha=0, \beta=\pm\mu)\) are not coplanar
+ * when \(\lambda\neq\mu\).
+ *
+ *
+ * We define an ellipse on the sphere as the locus of points \(P\) such that the sum of
+ * their angular distance to two foci \(F_+\) and \(F_-\) is constant, all points being on
+ * the sphere. The relationship between the foci and the two half aperture angles \(\lambda\)
+ * and \(\mu\) with:
+ * \[\lambda \ge \mu \Rightarrow F_\pm\left(\begin{gather*}
+ * \pm\sin\delta\\
+ * 0\\
+ * \cos\delta
+ * \end{gather*}\right)
+ * \quad\text{with}\quad
+ * \cos\delta = \frac{\cos\lambda}{\cos\mu}\]
+ *
+ *
+ * and
+ * \[\mu \ge \lambda \Rightarrow F_\pm\left(\begin{gather*}
+ * 0\\
+ * \pm\sin\delta\\
+ * \cos\delta
+ * \end{gather*}\right)
+ * \quad\text{with}\quad
+ * \cos\delta = \frac{\cos\mu}{\cos\lambda}\]
+ *
+ *
+ * It can be shown that the previous definition is equivalent to define first a regular
+ * planar ellipse drawn on a plane \(z = z_0\) (\(z_0\) being an arbitrary strictly positive
+ * number, \(z_0=1\) being the simplest choice) with semi major axis \(a=z_0\tan\lambda\)
+ * and semi minor axis \(b=z_0\tan\mu\) and then to project it onto the sphere using a
+ * central projection:
+ * \[\left\{\begin{align*}
+ * \left(\frac{x}{z_0\tan\lambda}\right)^2 + \left(\frac{y}{z_0\tan\mu}\right)^2 &= \left(\frac{z}{z_0}\right)^2\\
+ * x^2 + y^2 + z^2 &= 1
+ * \end{align*}\right.\]
+ *
+ *
+ * Simplifying first equation by \(z_0\) and eliminating \(z^2\) in it using the second equation gives:
+ * \[\left\{\begin{align*}
+ * \left(\frac{x}{\sin\lambda}\right)^2 + \left(\frac{y}{\sin\mu}\right)^2 &= 1\\
+ * x^2 + y^2 + z^2 &= 1
+ * \end{align*}\right.\]
+ * which shows that the previous definition is also equivalent to define first a
+ * dimensionless planar ellipse on the \((x, y)\) plane and to project it onto the sphere
+ * using a projection along \(z\).
+ *
+ *
+ * Note however that despite the ellipse on the sphere can be computed as a projection
+ * of an ellipse on the \((x, y)\) plane, the foci of one ellipse are not the projection of the
+ * foci of the other ellipse. The foci on the plane are closer to each other by a factor
+ * \(\cos\mu\) than the projection of the foci \(F_+\) and \(F_-\)).
+ *
+ * @author Luc Maisonobe
+ * @since 10.1
+ */
+public class EllipticalFieldOfView extends SmoothFieldOfView {
+
+ /** Factory for derivatives. */
+ private static final DSFactory FACTORY = new DSFactory(1, 3);
+
+ /** FOV half aperture angle for spreading along X (i.e. rotation around +Y). */
+ private final double halfApertureAlongX;
+
+ /** FOV half aperture angle for spreading along Y (i.e. rotation around -X). */
+ private final double halfApertureAlongY;
+
+ /** tan(halfApertureAlongX). */
+ private final double tanX;
+
+ /** tan(halfApertureAlongX). */
+ private final double tanY;
+
+ /** Unit vector along major axis. */
+ private final Vector3D u;
+
+ /** First focus. */
+ private final Vector3D focus1;
+
+ /** Second focus. */
+ private final Vector3D focus2;
+
+ /** Cross product of foci. */
+ private final Vector3D crossF1F2;
+
+ /** Dot product of foci. */
+ private final double dotF1F2;
+
+ /** Half angle between foci. */
+ private final double gamma;
+
+ /** Scaling factor for normalizing ellipse points. */
+ private final double d;
+
+ /** Angular semi major axis. */
+ private double a;
+
+ /** Build a new instance.
+ *
+ * Using a suitable rotation, an elliptical Field Of View can be oriented such
+ * that the ellipse center is along the Zell axis, one of its principal
+ * axes is in the (Xell, Zell) plane and the other principal
+ * axis is in the (Yell, Zell) plane. Beware that the ellipse
+ * principal axis that spreads along the Yell direction corresponds to a
+ * rotation around -Xell axis and that the ellipse principal axis that
+ * spreads along the Xell direction corresponds to a rotation around
+ * +Yell axis. The naming convention used here is that the angles are
+ * named after the spreading axis.
+ *
+ * @param center direction of the FOV center (i.e. Zell),
+ * in spacecraft frame
+ * @param primaryMeridian vector defining the (+Xell, Zell)
+ * half-plane (it is allowed to have {@code primaryMeridian} not orthogonal to
+ * {@code center} as orthogonality will be fixed internally)
+ * @param halfApertureAlongX FOV half aperture angle defining the ellipse spreading
+ * along Xell (i.e. it corresponds to a rotation around +Yell)
+ * @param halfApertureAlongY FOV half aperture angle defining the ellipse spreading
+ * along Yell (i.e. it corresponds to a rotation around -Xell)
+ * @param margin angular margin to apply to the zone (if positive,
+ * the Field Of View will consider points slightly outside of the
+ * zone are still visible)
+ */
+ public EllipticalFieldOfView(final Vector3D center, final Vector3D primaryMeridian,
+ final double halfApertureAlongX, final double halfApertureAlongY,
+ final double margin) {
+
+ super(center, primaryMeridian, margin);
+
+ final Vector3D v;
+ final double b;
+ if (halfApertureAlongX >= halfApertureAlongY) {
+ u = getX();
+ v = getY();
+ a = halfApertureAlongX;
+ b = halfApertureAlongY;
+ } else {
+ u = getY();
+ v = getX().negate();
+ a = halfApertureAlongY;
+ b = halfApertureAlongX;
+ }
+
+ final double cos = FastMath.cos(a) / FastMath.cos(b);
+ final double sin = FastMath.sqrt(1 - cos * cos);
+
+ this.halfApertureAlongX = halfApertureAlongX;
+ this.halfApertureAlongY = halfApertureAlongY;
+ this.tanX = FastMath.tan(halfApertureAlongX);
+ this.tanY = FastMath.tan(halfApertureAlongY);
+ this.focus1 = new Vector3D(+sin, u, cos, getZ());
+ this.focus2 = new Vector3D(-sin, u, cos, getZ());
+ this.crossF1F2 = new Vector3D(-2 * sin * cos, v);
+ this.dotF1F2 = 2 * cos * cos - 1;
+ this.gamma = FastMath.acos(cos);
+ this.d = 1.0 / (1 - dotF1F2 * dotF1F2);
+
+ }
+
+ /** get the FOV half aperture angle for spreading along Xell (i.e. rotation around +Yell).
+ * @return FOV half aperture angle for spreading along Xell (i.e. rotation around +Yell
+ */
+ public double getHalfApertureAlongX() {
+ return halfApertureAlongX;
+ }
+
+ /** get the FOV half aperture angle for spreading along Yell (i.e. rotation around -Xell).
+ * @return FOV half aperture angle for spreading along Yell (i.e. rotation around -Xell)
+ */
+ public double getHalfApertureAlongY() {
+ return halfApertureAlongY;
+ }
+
+ /** Get first focus in spacecraft frame.
+ * @return first focus in spacecraft frame
+ */
+ public Vector3D getFocus1() {
+ return focus1;
+ }
+
+ /** Get second focus in spacecraft frame.
+ * @return second focus in spacecraft frame
+ */
+ public Vector3D getFocus2() {
+ return focus2;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public double offsetFromBoundary(final Vector3D lineOfSight, final double angularRadius,
+ final VisibilityTrigger trigger) {
+
+ final double margin = getMargin();
+ final double correctedRadius = trigger.radiusCorrection(angularRadius);
+ final double deadBand = margin + angularRadius;
+
+ // for faster computation, we start using only the surrounding cap, to filter out
+ // far away points (which correspond to most of the points if the Field Of View is small)
+ final double crudeDistance = Vector3D.angle(getZ(), lineOfSight) - a;
+ if (crudeDistance > deadBand + 0.01) {
+ // we know we are strictly outside of the zone,
+ // use the crude distance to compute the (positive) return value
+ return crudeDistance + correctedRadius - margin;
+ }
+
+ // we are close, we need to compute carefully the exact offset;
+ // we project the point to the closest zone boundary
+ final double d1 = Vector3D.angle(lineOfSight, focus1);
+ final double d2 = Vector3D.angle(lineOfSight, focus2);
+ final Vector3D closest = projectToBoundary(lineOfSight, d1, d2);
+
+ // compute raw offset as an accurate signed angle
+ final double rawOffset = FastMath.copySign(Vector3D.angle(lineOfSight, closest),
+ d1 + d2 - 2 * a);
+
+ return rawOffset + correctedRadius - getMargin();
+
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Vector3D projectToBoundary(final Vector3D lineOfSight) {
+ final double d1 = Vector3D.angle(lineOfSight, focus1);
+ final double d2 = Vector3D.angle(lineOfSight, focus2);
+ return projectToBoundary(lineOfSight, d1, d2);
+ }
+
+ /** Find the direction on Field Of View Boundary closest to a line of sight.
+ * @param lineOfSight line of sight from the center of the Field Of View support
+ * unit sphere to the target in spacecraft frame
+ * @param d1 distance to first focus
+ * @param d2 distance to second focus
+ * @return direction on Field Of View Boundary closest to a line of sight
+ */
+ private Vector3D projectToBoundary(final Vector3D lineOfSight, final double d1, final double d2) {
+
+ final Vector3D los = lineOfSight.normalize();
+ final double side = Vector3D.dotProduct(los, crossF1F2);
+ if (FastMath.abs(side) < 1.0e-12) {
+ // the line of sight is almost along the major axis
+ return directionAt(Vector3D.dotProduct(los, u) > 0 ? 0.0 : FastMath.PI);
+ }
+
+ // find an initial point on ellipse, that approximates closest point
+ final double offset0 = 0.5 * (d1 - d2);
+ double minOffset = -gamma;
+ double maxOffset = +gamma;
+
+ // find closest ellipse point
+ DerivativeStructure offset = FACTORY.variable(0, offset0);
+ for (int i = 0; i < 100; i++) { // this loop usually converges in 1-4 iterations
+
+ // distance function we want to minimize
+ final FieldVector3D pn = directionAt(offset.add(a), offset.subtract(a).negate(), side);
+ final DerivativeStructure yn = FieldVector3D.angle(pn, los);
+ if (yn.getValue() < 1.0e-12) {
+ // the query point is almost on the ellipse boundary
+ break;
+ }
+
+ // Halley's iteration on the derivative (since we want the minimum of the distance function)
+ final double f0 = yn.getPartialDerivative(1);
+ final double f1 = yn.getPartialDerivative(2);
+ final double f2 = yn.getPartialDerivative(3);
+ double dx = -2 * f0 * f1 / (2 * f1 * f1 - f0 * f2);
+ if (dx * f0 > 0) {
+ // the Halley's iteration is going towards maximum, not minimum
+ // try to go past inflection point
+ dx = -1.5 * f2 / f1;
+ }
+
+ // manage bounds
+ if (dx < 0) {
+ maxOffset = offset.getValue();
+ if (offset.getValue() + dx <= minOffset) {
+ // we overshoot limit, fall back to bisection
+ dx = 0.5 * (minOffset - offset.getValue());
+ }
+ } else {
+ minOffset = offset.getValue();
+ if (offset.getValue() + dx >= maxOffset) {
+ // we overshoot limit, fall back to bisection
+ dx = 0.5 * (maxOffset - offset.getValue());
+ }
+ }
+
+ // apply offset change
+ offset = offset.add(dx);
+
+ // check convergence
+ if (FastMath.abs(dx) < 1.0e-12) {
+ break;
+ }
+
+ }
+
+ return directionAt(a + offset.getReal(), a - offset.getReal(), side);
+
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected Vector3D directionAt(final double angle) {
+ final SinCos sce = FastMath.sinCos(angle);
+ final Vector3D dEll = new Vector3D(tanX * sce.cos(), tanY * sce.sin(), 1.0).normalize();
+ return new Vector3D(dEll.getX(), getX(), dEll.getY(), getY(), dEll.getZ(), getZ());
+ }
+
+ /** Get a direction from distances to foci.
+ *
+ * if {@code d1} + {@code d2} = 2 max({@link #getHalfApertureAlongX()}, {@link #getHalfApertureAlongY()}),
+ * then the point is on the ellipse boundary
+ *
+ * @param d1 distance to focus 1
+ * @param d2 distance to focus 2
+ * @param sign sign of the ellipse point with respect to F1 ^ F2
+ * @return direction
+ */
+ private Vector3D directionAt(final double d1, final double d2, final double sign) {
+ final double cos1 = FastMath.cos(d1);
+ final double cos2 = FastMath.cos(d2);
+ final double a1 = (cos1 - cos2 * dotF1F2) * d;
+ final double a2 = (cos2 - cos1 * dotF1F2) * d;
+ final double ac = FastMath.sqrt((1 - (a1 * a1 + 2 * a1 * a2 * dotF1F2 + a2 * a2)) * d);
+ return new Vector3D(a1, focus1, a2, focus2, FastMath.copySign(ac, sign), crossF1F2);
+ }
+
+ /** Get a direction from distances to foci.
+ *
+ * if {@code d1} + {@code d2} = 2 max({@link #getHalfApertureAlongX()}, {@link #getHalfApertureAlongY()}),
+ * then the point is on the ellipse boundary
+ *
+ * @param d1 distance to focus 1
+ * @param d2 distance to focus 2
+ * @param sign sign of the ellipse point with respect to F1 ^ F2
+ * @param type of the field element
+ * @return direction
+ */
+ private > FieldVector3D directionAt(final T d1, final T d2, final double sign) {
+ final T cos1 = FastMath.cos(d1);
+ final T cos2 = FastMath.cos(d2);
+ final T a1 = cos1.subtract(cos2.multiply(dotF1F2)).multiply(d);
+ final T a2 = cos2.subtract(cos1.multiply(dotF1F2)).multiply(d);
+ final T ac = FastMath.sqrt(a1.multiply(a1.add(a2.multiply(2 * dotF1F2))).add(a2.multiply(a2)).negate().add(1).multiply(d));
+ return new FieldVector3D<>(a1, focus1, a2, focus2, FastMath.copySign(ac, sign), crossF1F2);
+ }
+
+}
diff --git a/src/main/java/org/orekit/geometry/fov/FieldOfView.java b/src/main/java/org/orekit/geometry/fov/FieldOfView.java
new file mode 100644
index 0000000000000000000000000000000000000000..ddf6cbc461b70271b9f1fd58402e746feb6af807
--- /dev/null
+++ b/src/main/java/org/orekit/geometry/fov/FieldOfView.java
@@ -0,0 +1,150 @@
+/* Copyright 2002-2019 CS Systèmes d'Information
+ * Licensed to CS Systèmes d'Information (CS) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.geometry.fov;
+
+import java.util.List;
+
+import org.hipparchus.geometry.euclidean.threed.Vector3D;
+import org.orekit.bodies.GeodeticPoint;
+import org.orekit.bodies.OneAxisEllipsoid;
+import org.orekit.frames.Transform;
+import org.orekit.propagation.events.VisibilityTrigger;
+
+/** Interface representing a spacecraft sensor Field Of View.
+ * Fields Of View are zones defined on the unit sphere centered on the
+ * spacecraft. Different implementations may use specific modeling
+ * depending on the shape.
+ * @author Luc Maisonobe
+ * @since 10.1
+ */
+public interface FieldOfView {
+
+ /** Get the angular margin to apply (radians).
+ * If angular margin is positive, points outside of the raw FoV but close
+ * enough to the boundary are considered visible. If angular margin is negative,
+ * points inside of the raw FoV but close enough to the boundary are considered
+ * not visible
+ * @return angular margin
+ * @see #offsetFromBoundary(Vector3D, double, VisibilityTrigger)
+ */
+ double getMargin();
+
+ /** Get the offset of target body with respect to the Field Of View Boundary.
+ *
+ * The offset is the signed angular distance between target body and closest boundary
+ * point, taking into account {@link VisibilityTrigger} and {@link #getMargin() margin}.
+ *
+ *
+ * As Field Of View can have complex shapes that may require long computation,
+ * when the target point can be proven to be outside of the Field Of View, a
+ * faster but approximate computation can be used. This approximation is only
+ * performed about 0.01 radians outside of the Field Of View augmented by the
+ * deadband defined by target body radius and Field Of View margin and should be
+ * designed to still return a positive value if the full accurate computation
+ * would return a positive value. When target point is close to the zone (and
+ * furthermore when it is inside the zone), the full accurate computation is
+ * performed. This design allows this offset to be used as a reliable way to
+ * detect Field Of View boundary crossings (taking {@link VisibilityTrigger}
+ * and {@link #getMargin() margin} into account), which correspond to sign
+ * changes of the offset.
+ *
+ * @param lineOfSight line of sight from the center of the Field Of View support
+ * unit sphere to the target in spacecraft frame
+ * @param angularRadius target body angular radius
+ * @param trigger visibility trigger for spherical bodies
+ * @return an offset negative if the target is visible within the Field Of
+ * View and positive if it is outside of the Field Of View
+ * (note that this cannot take into account interposing bodies)
+ * @see #offsetFromBoundary(Vector3D, double, VisibilityTrigger)
+ */
+ double offsetFromBoundary(Vector3D lineOfSight, double angularRadius, VisibilityTrigger trigger);
+
+ /** Find the direction on Field Of View Boundary closest to a line of sight.
+ * @param lineOfSight line of sight from the center of the Field Of View support
+ * unit sphere to the target in spacecraft frame
+ * @return direction on Field Of View Boundary closest to a line of sight
+ */
+ Vector3D projectToBoundary(Vector3D lineOfSight);
+
+ /** Get the footprint of the Field Of View on ground.
+ *
+ * This method assumes the Field Of View is centered on some carrier,
+ * which will typically be a spacecraft or a ground station antenna.
+ * The points in the footprint boundary loops are all at altitude zero
+ * with respect to the ellipsoid, they correspond either to projection
+ * on ground of the edges of the Field Of View, or to points on the body
+ * limb if the Field Of View goes past horizon. The points on the limb
+ * see the carrier origin at zero elevation. If the Field Of View is so
+ * large it contains entirely the body, all points will correspond to
+ * points at limb. If the Field Of View looks away from body, the
+ * boundary loops will be an empty list. The points within footprint
+ * loops are sorted in trigonometric order as seen from the carrier.
+ * This implies that someone traveling on ground from one point to the
+ * next one will have the points visible from the carrier on his left
+ * hand side, and the points not visible from the carrier on his right
+ * hand side.
+ *
+ *
+ * The truncation of Field Of View at limb can induce strange results
+ * for complex Fields Of View. If for example a Field Of View is a
+ * ring with a hole and part of the ring goes past horizon, then instead
+ * of having a single loop with a C-shaped boundary, the method will
+ * still return two loops truncated at the limb, one clockwise and one
+ * counterclockwise, hence "closing" the C-shape twice. This behavior
+ * is considered acceptable.
+ *
+ *
+ * If the carrier is a spacecraft, then the {@code fovToBody} transform
+ * can be computed from a {@link org.orekit.propagation.SpacecraftState}
+ * as follows:
+ *
+ *
+ * Transform inertToBody = state.getFrame().getTransformTo(body.getBodyFrame(), state.getDate());
+ * Transform fovToBody = new Transform(state.getDate(),
+ * state.toTransform().getInverse(),
+ * inertToBody);
+ *
+ *
+ * If the carrier is a ground station, located using a topocentric frame
+ * and managing its pointing direction using a transform between the
+ * dish frame and the topocentric frame, then the {@code fovToBody} transform
+ * can be computed as follows:
+ *
+ *
+ * Transform topoToBody = topocentricFrame.getTransformTo(body.getBodyFrame(), date);
+ * Transform topoToDish = ...
+ * Transform fovToBody = new Transform(date,
+ * topoToDish.getInverse(),
+ * topoToBody);
+ *
+ *
+ * Only the raw zone is used, the angular margin is ignored here.
+ *
+ * @param fovToBody transform between the frame in which the Field Of View
+ * is defined and body frame.
+ * @param body body surface the Field Of View will be projected on
+ * @param angularStep step used for boundary loops sampling (radians),
+ * beware this is generally not an angle on the unit sphere, but rather a
+ * phase angle used by the underlying Field Of View boundary model
+ * @return list footprint boundary loops (there may be several independent
+ * loops if the Field Of View shape is complex)
+ */
+ List> getFootprint(Transform fovToBody,
+ OneAxisEllipsoid body,
+ double angularStep);
+
+}
diff --git a/src/main/java/org/orekit/geometry/fov/PolygonalFieldOfView.java b/src/main/java/org/orekit/geometry/fov/PolygonalFieldOfView.java
new file mode 100644
index 0000000000000000000000000000000000000000..638a76933e3f323a7c0e2909575d3edfeb3e8e87
--- /dev/null
+++ b/src/main/java/org/orekit/geometry/fov/PolygonalFieldOfView.java
@@ -0,0 +1,333 @@
+/* Copyright 2002-2019 CS Systèmes d'Information
+ * Licensed to CS Systèmes d'Information (CS) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.geometry.fov;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.hipparchus.geometry.enclosing.EnclosingBall;
+import org.hipparchus.geometry.euclidean.threed.Line;
+import org.hipparchus.geometry.euclidean.threed.Rotation;
+import org.hipparchus.geometry.euclidean.threed.RotationConvention;
+import org.hipparchus.geometry.euclidean.threed.Vector3D;
+import org.hipparchus.geometry.partitioning.Region;
+import org.hipparchus.geometry.spherical.twod.Edge;
+import org.hipparchus.geometry.spherical.twod.S2Point;
+import org.hipparchus.geometry.spherical.twod.Sphere2D;
+import org.hipparchus.geometry.spherical.twod.SphericalPolygonsSet;
+import org.hipparchus.geometry.spherical.twod.Vertex;
+import org.hipparchus.util.FastMath;
+import org.hipparchus.util.MathUtils;
+import org.hipparchus.util.SinCos;
+import org.orekit.bodies.GeodeticPoint;
+import org.orekit.bodies.OneAxisEllipsoid;
+import org.orekit.errors.OrekitException;
+import org.orekit.errors.OrekitMessages;
+import org.orekit.frames.Frame;
+import org.orekit.frames.Transform;
+import org.orekit.propagation.events.VisibilityTrigger;
+
+/** Class representing a spacecraft sensor Field Of View with polygonal shape.
+ * Fields Of View are zones defined on the unit sphere centered on the
+ * spacecraft. They can have any shape, they can be split in several
+ * non-connected patches and can have holes.
+ * @author Luc Maisonobe
+ * @since 10.1
+ */
+public class PolygonalFieldOfView extends AbstractFieldOfView {
+
+ /** Spherical zone. */
+ private final SphericalPolygonsSet zone;
+
+ /** Spherical cap surrounding the zone. */
+ private final EnclosingBall cap;
+
+ /** Build a new instance.
+ * @param zone interior of the Field Of View, in spacecraft frame
+ * @param margin angular margin to apply to the zone (if positive,
+ * points outside of the raw FoV but close enough to the boundary are
+ * considered visible; if negative, points inside of the raw FoV
+ * but close enough to the boundary are considered not visible)
+ */
+ public PolygonalFieldOfView(final SphericalPolygonsSet zone, final double margin) {
+ super(margin);
+ this.zone = zone;
+ this.cap = zone.getEnclosingCap();
+ }
+
+ /** Build Field Of View with a regular polygon shape.
+ * @param center center of the polygon (the center is in the inside part)
+ * @param meridian point defining the reference meridian for middle of first edge
+ * @param insideRadius distance of the edges middle points to the center
+ * (the polygon vertices will therefore be farther away from the center)
+ * @param n number of sides of the polygon
+ * @param margin angular margin to apply to the zone (if positive,
+ * points outside of the raw FoV but close enough to the boundary are
+ * considered visible; if negative, points inside of the raw FoV
+ * but close enough to the boundary are considered not visible)
+ * @deprecated as of 10.1, replaced by {@link #PolygonalFieldOfView(Vector3D,
+ * DefiningConeType, Vector3D, double, int, double)}
+ */
+ @Deprecated
+ public PolygonalFieldOfView(final Vector3D center, final Vector3D meridian,
+ final double insideRadius, final int n,
+ final double margin) {
+ this(center, DefiningConeType.INSIDE_CONE_TOUCHING_POLYGON_AT_EDGES_MIDDLE,
+ meridian, insideRadius, n, margin);
+ }
+
+ /** Build Field Of View with a regular polygon shape.
+ * @param center center of the polygon (the center is in the inside part)
+ * @param coneType type of defining cone
+ * @param meridian point defining the reference meridian for one contact
+ * point between defining cone and polygon (i.e. either a polygon edge
+ * middle point or a polygon vertex)
+ * @param radius defining cone angular radius
+ * @param n number of sides of the polygon
+ * @param margin angular margin to apply to the zone (if positive,
+ * points outside of the raw FoV but close enough to the boundary are
+ * considered visible; if negative, points inside of the raw FoV
+ * but close enough to the boundary are considered not visible)
+ * @since 10.1
+ */
+ public PolygonalFieldOfView(final Vector3D center, final DefiningConeType coneType,
+ final Vector3D meridian, final double radius,
+ final int n, final double margin) {
+
+ super(margin);
+
+ final double verticesRadius = coneType.verticesRadius(radius, n);
+ final Vector3D vertex = coneType.createVertex(center, meridian, verticesRadius, n);
+ this.zone = new SphericalPolygonsSet(center, vertex, verticesRadius,
+ n, 1.0e-12 * verticesRadius);
+
+ final Rotation r = new Rotation(center, MathUtils.TWO_PI / n, RotationConvention.VECTOR_OPERATOR);
+ final S2Point[] support = new S2Point[n];
+ support[0] = new S2Point(vertex);
+ for (int i = 1; i < n; ++i) {
+ support[i] = new S2Point(r.applyTo(support[i - 1].getVector()));
+ }
+ this.cap = new EnclosingBall<>(new S2Point(center), Vector3D.angle(center, vertex), support);
+
+ }
+
+ /** Get the interior zone.
+ * @return the interior zone
+ */
+ public SphericalPolygonsSet getZone() {
+ return zone;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public double offsetFromBoundary(final Vector3D lineOfSight, final double angularRadius,
+ final VisibilityTrigger trigger) {
+
+ final S2Point los = new S2Point(lineOfSight);
+ final double margin = getMargin();
+ final double correctedRadius = trigger.radiusCorrection(angularRadius);
+ final double deadBand = margin + angularRadius;
+
+ // for faster computation, we start using only the surrounding cap, to filter out
+ // far away points (which correspond to most of the points if the Field Of View is small)
+ final double crudeDistance = cap.getCenter().distance(los) - cap.getRadius();
+ if (crudeDistance > deadBand + 0.01) {
+ // we know we are strictly outside of the zone,
+ // use the crude distance to compute the (positive) return value
+ return crudeDistance + correctedRadius - margin;
+ }
+
+ // we are close, we need to compute carefully the exact offset;
+ // we project the point to the closest zone boundary
+ return zone.projectToBoundary(los).getOffset() + correctedRadius - margin;
+
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Vector3D projectToBoundary(final Vector3D lineOfSight) {
+ return ((S2Point) zone.projectToBoundary(new S2Point(lineOfSight)).getProjected()).getVector();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public List> getFootprint(final Transform fovToBody,
+ final OneAxisEllipsoid body,
+ final double angularStep) {
+
+ final Frame bodyFrame = body.getBodyFrame();
+ final Vector3D position = fovToBody.transformPosition(Vector3D.ZERO);
+ final double r = position.getNorm();
+ if (body.isInside(position)) {
+ throw new OrekitException(OrekitMessages.POINT_INSIDE_ELLIPSOID);
+ }
+
+ final List> footprint = new ArrayList<>();
+
+ final List boundary = zone.getBoundaryLoops();
+ for (final Vertex loopStart : boundary) {
+ int count = 0;
+ final List loop = new ArrayList<>();
+ boolean intersectionsFound = false;
+ for (Edge edge = loopStart.getOutgoing();
+ count == 0 || edge.getStart() != loopStart;
+ edge = edge.getEnd().getOutgoing()) {
+ ++count;
+ final int n = (int) FastMath.ceil(edge.getLength() / angularStep);
+ final double delta = edge.getLength() / n;
+ for (int i = 0; i < n; ++i) {
+ final Vector3D awaySC = new Vector3D(r, edge.getPointAt(i * delta));
+ final Vector3D awayBody = fovToBody.transformPosition(awaySC);
+ final Line lineOfSight = new Line(position, awayBody, 1.0e-3);
+ GeodeticPoint gp = body.getIntersectionPoint(lineOfSight, position,
+ bodyFrame, null);
+ if (gp != null &&
+ Vector3D.dotProduct(awayBody.subtract(position),
+ body.transform(gp).subtract(position)) < 0) {
+ // the intersection is in fact on the half-line pointing
+ // towards the back side, it is a spurious intersection
+ gp = null;
+ }
+
+ if (gp != null) {
+ // the line of sight does intersect the body
+ intersectionsFound = true;
+ } else {
+ // the line of sight does not intersect body
+ // we use a point on the limb
+ gp = body.transform(body.pointOnLimb(position, awayBody), bodyFrame, null);
+ }
+
+ // add the point in front of the list
+ // (to ensure the loop will be in trigonometric orientation)
+ loop.add(0, gp);
+
+ }
+ }
+
+ if (intersectionsFound) {
+ // at least some of the points did intersect the body,
+ // this loop contributes to the footprint
+ footprint.add(loop);
+ }
+
+ }
+
+ if (footprint.isEmpty()) {
+ // none of the Field Of View loops cross the body
+ // either the body is outside of Field Of View, or it is fully contained
+ // we check the center
+ final Vector3D bodyCenter = fovToBody.getInverse().transformPosition(Vector3D.ZERO);
+ if (zone.checkPoint(new S2Point(bodyCenter)) != Region.Location.OUTSIDE) {
+ // the body is fully contained in the Field Of View
+ // we use the full limb as the footprint
+ final Vector3D x = bodyCenter.orthogonal();
+ final Vector3D y = Vector3D.crossProduct(bodyCenter, x).normalize();
+ final double sinEta = body.getEquatorialRadius() / r;
+ final double sinEta2 = sinEta * sinEta;
+ final double cosAlpha = (FastMath.cos(angularStep) + sinEta2 - 1) / sinEta2;
+ final int n = (int) FastMath.ceil(MathUtils.TWO_PI / FastMath.acos(cosAlpha));
+ final double delta = MathUtils.TWO_PI / n;
+ final List loop = new ArrayList<>(n);
+ for (int i = 0; i < n; ++i) {
+ final Vector3D outside = new Vector3D(r * FastMath.cos(i * delta), x,
+ r * FastMath.sin(i * delta), y);
+ loop.add(body.transform(body.pointOnLimb(position, outside), bodyFrame, null));
+ }
+ footprint.add(loop);
+ }
+ }
+
+ return footprint;
+
+ }
+
+ /** Enumerate for cone/polygon relative position.
+ * @since 10.1
+ */
+ public enum DefiningConeType {
+
+ /** Constant for cones inside polygons and touching it at polygon edges middle points. */
+ INSIDE_CONE_TOUCHING_POLYGON_AT_EDGES_MIDDLE() {
+
+ /** {@inheritDoc}*/
+ @Override
+ protected double verticesRadius(final double radius, final int n) {
+ // convert the inside (edges middle points) radius to outside (vertices) radius
+ return FastMath.atan(FastMath.tan(radius) / FastMath.cos(FastMath.PI / n));
+ }
+
+ /** {@inheritDoc}*/
+ @Override
+ protected Vector3D createVertex(final Vector3D center, final Vector3D meridian,
+ final double verticesRadius, final int n) {
+ // convert the edge middle meridian to a vertex
+ final SinCos scA = FastMath.sinCos(FastMath.PI / n);
+ final SinCos scR = FastMath.sinCos(verticesRadius);
+ final Vector3D z = center.normalize();
+ final Vector3D y = Vector3D.crossProduct(center, meridian).normalize();
+ final Vector3D x = Vector3D.crossProduct(y, z);
+ return new Vector3D(scR.sin() * scA.cos(), x, scR.sin() * scA.sin(), y, scR.cos(), z);
+ }
+
+ },
+
+ /** Constant for cones outside polygons and touching it at polygon vertices. */
+ OUTSIDE_CONE_TOUCHING_POLYGON_AT_VERTICES() {
+
+ /** {@inheritDoc}*/
+ @Override
+ protected double verticesRadius(final double radius, final int n) {
+ return radius;
+ }
+
+ /** {@inheritDoc}*/
+ @Override
+ protected Vector3D createVertex(final Vector3D center, final Vector3D meridian,
+ final double verticesRadius, final int n) {
+ // convert the vertex meridian to a vertex
+ final SinCos scR = FastMath.sinCos(verticesRadius);
+ final Vector3D z = center.normalize();
+ final Vector3D y = Vector3D.crossProduct(center, meridian).normalize();
+ final Vector3D x = Vector3D.crossProduct(y, z);
+ return new Vector3D(scR.sin(), x, scR.cos(), z);
+ }
+
+ };
+
+ /** Compute radius of cone going through vertices.
+ * @param radius defining cone angular radius
+ * @param n number of sides of the polygon
+ * @return radius of cone going through vertices
+ */
+ protected abstract double verticesRadius(double radius, int n);
+
+ /** Create a vertex.
+ * @param center center of the polygon (the center is in the inside part)
+ * @param meridian point defining the reference meridian for one contact
+ * point between defining cone and polygon (i.e. either a polygon edge
+ * middle point or a polygon vertex)
+ * @param verticesRadius defining radius of cone passing through vertices
+ * @param n number of sides of the polygon
+ * @return created vertex
+ */
+ protected abstract Vector3D createVertex(Vector3D center, Vector3D meridian,
+ double verticesRadius, int n);
+
+ }
+
+}
diff --git a/src/main/java/org/orekit/geometry/fov/SmoothFieldOfView.java b/src/main/java/org/orekit/geometry/fov/SmoothFieldOfView.java
new file mode 100644
index 0000000000000000000000000000000000000000..f85517f557444fefbd03fc8dada5a0403c61ff91
--- /dev/null
+++ b/src/main/java/org/orekit/geometry/fov/SmoothFieldOfView.java
@@ -0,0 +1,175 @@
+/* Copyright 2002-2019 CS Systèmes d'Information
+ * Licensed to CS Systèmes d'Information (CS) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.geometry.fov;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.hipparchus.geometry.euclidean.threed.Line;
+import org.hipparchus.geometry.euclidean.threed.Vector3D;
+import org.hipparchus.util.FastMath;
+import org.hipparchus.util.MathUtils;
+import org.orekit.bodies.GeodeticPoint;
+import org.orekit.bodies.OneAxisEllipsoid;
+import org.orekit.errors.OrekitException;
+import org.orekit.errors.OrekitMessages;
+import org.orekit.frames.Frame;
+import org.orekit.frames.Transform;
+import org.orekit.propagation.events.VisibilityTrigger;
+
+/** Class representing a spacecraft sensor Field Of View with shape defined by a smooth single loop.
+ * @author Luc Maisonobe
+ * @since 10.1
+ */
+public abstract class SmoothFieldOfView extends AbstractFieldOfView {
+
+ /** Direction of the FOV center. */
+ private final Vector3D center;
+
+ /** X axis defining FoV boundary. */
+ private final Vector3D xAxis;
+
+ /** Y axis defining FoV boundary. */
+ private final Vector3D yAxis;
+
+ /** Z axis defining FoV boundary. */
+ private final Vector3D zAxis;
+
+ /** Build a new instance.
+ * @param center direction of the FOV center (Zsmooth), in spacecraft frame
+ * @param primaryMeridian vector defining the (+Xsmooth, Zsmooth)
+ * half-plane (it is allowed to have {@code primaryMeridian} not orthogonal to
+ * {@code center} as orthogonality will be fixed internally)
+ * @param margin angular margin to apply to the zone (if positive,
+ * the Field Of View will consider points slightly outside of the
+ * zone are still visible)
+ */
+ protected SmoothFieldOfView(final Vector3D center, final Vector3D primaryMeridian,
+ final double margin) {
+
+ super(margin);
+
+ this.center = center;
+ this.zAxis = center.normalize();
+ this.yAxis = Vector3D.crossProduct(center, primaryMeridian).normalize();
+ this.xAxis = Vector3D.crossProduct(yAxis, center).normalize();
+
+ }
+
+ /** Get the direction of the FOV center, in spacecraft frame.
+ * @return direction of the FOV center, in spacecraft frame
+ */
+ public Vector3D getCenter() {
+ return center;
+ }
+
+ /** Get the X axis defining FoV boundary.
+ * @return X axis defining FoV boundary, in spacecraft frame
+ */
+ public Vector3D getX() {
+ return xAxis;
+ }
+
+ /** Get the Y axis defining FoV boundary.
+ * @return Y axis defining FoV boundary, in spacecraft frame
+ */
+ public Vector3D getY() {
+ return yAxis;
+ }
+
+ /** Get the Z axis defining FoV boundary.
+ * @return Z axis defining FoV boundary, in spacecraft frame
+ */
+ public Vector3D getZ() {
+ return zAxis;
+ }
+
+
+ /** {@inheritDoc} */
+ @Override
+ public List> getFootprint(final Transform fovToBody,
+ final OneAxisEllipsoid body,
+ final double angularStep) {
+
+ final Frame bodyFrame = body.getBodyFrame();
+ final Vector3D position = fovToBody.transformPosition(Vector3D.ZERO);
+ final double r = position.getNorm();
+ if (body.isInside(position)) {
+ throw new OrekitException(OrekitMessages.POINT_INSIDE_ELLIPSOID);
+ }
+
+ // prepare loop around FoV
+ boolean intersectionsFound = false;
+ final int nbPoints = (int) FastMath.ceil(MathUtils.TWO_PI / angularStep);
+ final List loop = new ArrayList<>(nbPoints);
+
+ // loop in inverse trigonometric order, so footprint is in trigonometric order
+ final double step = MathUtils.TWO_PI / nbPoints;
+ for (int i = 0; i < nbPoints; ++i) {
+ final Vector3D direction = directionAt(-i * step);
+ final Vector3D awaySC = new Vector3D(r, direction);
+ final Vector3D awayBody = fovToBody.transformPosition(awaySC);
+ final Line lineOfSight = new Line(position, awayBody, 1.0e-3);
+ GeodeticPoint gp = body.getIntersectionPoint(lineOfSight, position, bodyFrame, null);
+ if (gp != null &&
+ Vector3D.dotProduct(awayBody.subtract(position), body.transform(gp).subtract(position)) < 0) {
+ // the intersection is in fact on the half-line pointing
+ // towards the back side, it is a spurious intersection
+ gp = null;
+ }
+
+ if (gp != null) {
+ // the line of sight does intersect the body
+ intersectionsFound = true;
+ } else {
+ // the line of sight does not intersect body
+ // we use a point on the limb
+ gp = body.transform(body.pointOnLimb(position, awayBody), bodyFrame, null);
+ }
+
+ // add the point
+ loop.add(gp);
+
+ }
+
+ final List> footprint = new ArrayList<>();
+ if (intersectionsFound) {
+ // at least some of the points did intersect the body, there is a footprint
+ footprint.add(loop);
+ } else {
+ // the Field Of View loop does not cross the body
+ // either the body is outside of Field Of View, or it is fully contained
+ // we check the center
+ final Vector3D bodyCenter = fovToBody.getInverse().transformPosition(Vector3D.ZERO);
+ if (offsetFromBoundary(bodyCenter, 0.0, VisibilityTrigger.VISIBLE_ONLY_WHEN_FULLY_IN_FOV) < 0.0) {
+ // the body is fully contained in the Field Of View
+ // the previous loop did compute the full limb as the footprint
+ footprint.add(loop);
+ }
+ }
+
+ return footprint;
+
+ }
+
+ /** Get boundary direction at angle.
+ * @param angle phase angle of the boundary direction
+ * @return boundary direction at phase angle in spacecraft frame
+ */
+ protected abstract Vector3D directionAt(double angle);
+
+}
diff --git a/src/main/java/org/orekit/gnss/CombinedObservationData.java b/src/main/java/org/orekit/gnss/CombinedObservationData.java
new file mode 100644
index 0000000000000000000000000000000000000000..dd5742c017c8ce529abc434d29254110325cba71
--- /dev/null
+++ b/src/main/java/org/orekit/gnss/CombinedObservationData.java
@@ -0,0 +1,105 @@
+/* Copyright 2002-2019 CS Systèmes d'Information
+ * Licensed to CS Systèmes d'Information (CS) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.gnss;
+
+import java.util.List;
+
+import org.orekit.estimation.measurements.gnss.CombinationType;
+
+/**
+ * Combined observation data.
+ * @author Bryan Cazabonne
+ * @since 10.1
+ */
+public class CombinedObservationData {
+
+ /** Type of the combination of measurements. */
+ private final CombinationType combinationType;
+
+ /** Measurement type. */
+ private final MeasurementType measurementType;
+
+ /** Combined observed value. */
+ private double value;
+
+ /** Frequency of the combined observation data [MHz]. */
+ private final double combinedFrequency;
+
+ /** Observation data used to perform the combination of measurements. */
+ private final List usedData;
+
+ /**
+ * Constructor.
+ * @param combinationType combination of measurements used to build the combined observation data
+ * @param measurementType measurement type used for the combination of measurement
+ * @param combinedValue combined observed value
+ * (may be {@code Double.NaN} if combined observation not available)
+ * @param combinedFrequency frequency of the combined observation data in MHz
+ * (may be {@code Double.NaN} if combined frequency is not available)
+ * @param usedData observation data used to perform the combination of measurements
+ */
+ public CombinedObservationData(final CombinationType combinationType, final MeasurementType measurementType,
+ final double combinedValue, final double combinedFrequency,
+ final List usedData) {
+ this.combinationType = combinationType;
+ this.measurementType = measurementType;
+ this.value = combinedValue;
+ this.combinedFrequency = combinedFrequency;
+ this.usedData = usedData;
+ }
+
+ /** Get the combined observed value.
+ * @return observed value (may be {@code Double.NaN} if observation not available)
+ */
+ public double getValue() {
+ return value;
+ }
+
+ /** Get the value of the combined frequency in MHz.
+ *
+ * For the single frequency combinations, this method returns
+ * the common frequency of both measurements.
+ *
+ * @return value of the combined frequency in MHz
+ */
+ public double getCombinedMHzFrequency() {
+ return combinedFrequency;
+ }
+
+ /** Get the type of the combination of measurements used to build the instance.
+ * @return the combination of measurements type
+ */
+ public CombinationType getCombinationType() {
+ return combinationType;
+ }
+
+ /** Get the measurement type.
+ * @return measurement type
+ */
+ public MeasurementType getMeasurementType() {
+ return measurementType;
+ }
+
+ /**
+ * Get the list of observation data used to perform the combination of measurements.
+ * @return a list of observation data
+ */
+ public List getUsedObservationData() {
+ return usedData;
+ }
+
+}
diff --git a/src/main/java/org/orekit/gnss/CombinedObservationDataSet.java b/src/main/java/org/orekit/gnss/CombinedObservationDataSet.java
new file mode 100644
index 0000000000000000000000000000000000000000..7d10939a63d4e93c5c5ad553f4b39caf5eb1fecd
--- /dev/null
+++ b/src/main/java/org/orekit/gnss/CombinedObservationDataSet.java
@@ -0,0 +1,112 @@
+/* Copyright 2002-2019 CS Systèmes d'Information
+ * Licensed to CS Systèmes d'Information (CS) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.gnss;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.orekit.time.AbsoluteDate;
+import org.orekit.time.TimeStamped;
+
+/**
+ * Combined observation data set.
+ * @author Bryan Cazabonne
+ * @since 10.1
+ */
+public class CombinedObservationDataSet implements TimeStamped {
+
+ /** Rinex header associated with this data set. */
+ private final RinexHeader header;
+
+ /** Satellite System. */
+ private final SatelliteSystem satelliteSystem;
+
+ /** PRN Number of the satellite observed. */
+ private final int prnNumber;
+
+ /** Date of the observation. */
+ private final AbsoluteDate tObs;
+
+ /** List of Observation data. */
+ private final List observationData;
+
+ /** Receiver clock offset (seconds). */
+ private final double rcvrClkOffset;
+
+ /**
+ * Simple constructor.
+ * @param header Rinex header associated with this data set
+ * @param satelliteSystem Satellite system
+ * @param prnNumber PRN number
+ * @param tObs Observation date
+ * @param rcvrClkOffset Receiver clock offset (optional, 0 by default)
+ * @param observationData List of combined observation data
+ */
+ public CombinedObservationDataSet(final RinexHeader header, final SatelliteSystem satelliteSystem,
+ final int prnNumber, final AbsoluteDate tObs,
+ final double rcvrClkOffset, final List observationData) {
+ this.header = header;
+ this.satelliteSystem = satelliteSystem;
+ this.prnNumber = prnNumber;
+ this.tObs = tObs;
+ this.observationData = observationData;
+ this.rcvrClkOffset = rcvrClkOffset;
+ }
+
+ /** Get the Rinex header associated with this data set.
+ * @return Rinex header associated with this data set
+ * @since 9.3
+ */
+ public RinexHeader getHeader() {
+ return header;
+ }
+
+ /** Get Satellite System.
+ * @return satellite system of observed satellite
+ */
+ public SatelliteSystem getSatelliteSystem() {
+ return satelliteSystem;
+ }
+
+ /** Get PRN number.
+ * @return PRN number of the observed satellite
+ */
+ public int getPrnNumber() {
+ return prnNumber;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public AbsoluteDate getDate() {
+ return tObs;
+ }
+
+ /** Get list of observation data.
+ * @return unmodifiable view of of observation data for the observed satellite
+ */
+ public List getObservationData() {
+ return Collections.unmodifiableList(observationData);
+ }
+
+ /** Get receiver clock offset.
+ * @return receiver clock offset (it is optional, may be 0)
+ */
+ public double getRcvrClkOffset() {
+ return rcvrClkOffset;
+ }
+
+}
diff --git a/src/main/java/org/orekit/gnss/Frequency.java b/src/main/java/org/orekit/gnss/Frequency.java
index 116b742496546eb94a8c2ed20f4d2f7787038a31..8ce9ad924a4bfadd1e5d92b0bc998a72a4537621 100644
--- a/src/main/java/org/orekit/gnss/Frequency.java
+++ b/src/main/java/org/orekit/gnss/Frequency.java
@@ -16,6 +16,8 @@
*/
package org.orekit.gnss;
+import org.orekit.utils.Constants;
+
/**
* Enumerate for GNSS frequencies.
*
@@ -43,6 +45,12 @@ public enum Frequency {
/** GLONASS, "G3" (1202.025 MHz). */
R03(SatelliteSystem.GLONASS, "G3", 117.5),
+ /** GLONASS, "G1a" (1600.995 MHZ). */
+ R04(SatelliteSystem.GLONASS, "G1a", 156.5),
+
+ /** GLONASS, "G2a" (1248.06 MHz). */
+ R06(SatelliteSystem.GLONASS, "G2a", 122),
+
/** Galileo, "E1" (1575.42 MHz). */
E01(SatelliteSystem.GALILEO, "E1", 154),
@@ -151,12 +159,22 @@ public enum Frequency {
}
/** Get the value of the frequency in MHz.
- * @return satellite system for which this frequency is defined
+ * @return value of the frequency in MHz
* @see #F0
* @see #getRatio()
+ * @see #getWavelength()
*/
public double getMHzFrequency() {
return ratio * F0;
}
+ /** Get the wavelength in meters.
+ * @return wavelength in meters
+ * @see #getMHzFrequency()
+ * @since 10.1
+ */
+ public double getWavelength() {
+ return Constants.SPEED_OF_LIGHT / (1.0e6 * getMHzFrequency());
+ }
+
}
diff --git a/src/main/java/org/orekit/gnss/HatanakaCompressFilter.java b/src/main/java/org/orekit/gnss/HatanakaCompressFilter.java
new file mode 100644
index 0000000000000000000000000000000000000000..2336002c84f6ec7c3dc11f561f27ec6f1ad881f4
--- /dev/null
+++ b/src/main/java/org/orekit/gnss/HatanakaCompressFilter.java
@@ -0,0 +1,1038 @@
+/* Copyright 2002-2019 CS Systèmes d'Information
+ * Licensed to CS Systèmes d'Information (CS) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.gnss;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.CharBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.hipparchus.util.FastMath;
+import org.orekit.data.DataFilter;
+import org.orekit.data.NamedData;
+import org.orekit.errors.OrekitException;
+import org.orekit.errors.OrekitMessages;
+
+/** Decompression filter for Hatanaka compressed RINEX files.
+ * @see A
+ * Compression Format and Tools for GNSS Observation Data
+ * @since 10.1
+ */
+public class HatanakaCompressFilter implements DataFilter {
+
+ /** Pattern for rinex 2 observation files. */
+ private static final Pattern RINEX_2_PATTERN = Pattern.compile("^(\\w{4}\\d{3}[0a-x](?:\\d{2})?\\.\\d{2})[dD]$");
+
+ /** Pattern for rinex 3 observation files. */
+ private static final Pattern RINEX_3_PATTERN = Pattern.compile("^(\\w{9}_\\w{1}_\\d{11}_\\d{2}\\w_\\d{2}\\w{1}_\\w{2})\\.crx$");
+
+ /** {@inheritDoc} */
+ @Override
+ public NamedData filter(final NamedData original) {
+
+ final String oName = original.getName();
+ final NamedData.StreamOpener oOpener = original.getStreamOpener();
+
+ final Matcher rinex2Matcher = RINEX_2_PATTERN.matcher(oName);
+ if (rinex2Matcher.matches()) {
+ // this is a rinex 2 file compressed with Hatanaka method
+ final String fName = rinex2Matcher.group(1) + "o";
+ final NamedData.StreamOpener fOpener = () -> new HatanakaInputStream(oName, oOpener.openStream());
+ return new NamedData(fName, fOpener);
+ }
+
+ final Matcher rinex3Matcher = RINEX_3_PATTERN.matcher(oName);
+ if (rinex3Matcher.matches()) {
+ // this is a rinex 3 file compressed with Hatanaka method
+ final String fName = rinex3Matcher.group(1) + ".rnx";
+ final NamedData.StreamOpener fOpener = () -> new HatanakaInputStream(oName, oOpener.openStream());
+ return new NamedData(fName, fOpener);
+ }
+
+ // it is not an Hatanaka compressed rinex file
+ return original;
+
+ }
+
+ /** Filtering of Hatanaka compressed stream. */
+ private static class HatanakaInputStream extends InputStream {
+
+ /** Format of the current file. */
+ private final CompactRinexFormat format;
+
+ /** Line-oriented input. */
+ private final BufferedReader reader;
+
+ /** Pending uncompressed output lines. */
+ private String pending;
+
+ /** Number of characters already output in pending lines. */
+ private int countOut;
+
+ /** Simple constructor.
+ * @param name file name
+ * @param input underlying compressed stream
+ * @exception IOException if first lines cannot be read
+ */
+ HatanakaInputStream(final String name, final InputStream input)
+ throws IOException {
+
+ reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
+
+ // check header
+ format = CompactRinexFormat.getFormat(name, reader);
+
+ pending = null;
+
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int read() throws IOException {
+
+ if (pending == null) {
+ // we need to read another section from the underlying stream and uncompress it
+ countOut = 0;
+ final String firstLine = reader.readLine();
+ if (firstLine == null) {
+ // there are no lines left
+ return -1;
+ } else {
+ pending = format.uncompressSection(firstLine);
+ }
+ }
+
+ if (countOut == pending.length()) {
+ // output an end of line
+ pending = null;
+ return '\n';
+ } else {
+ // output a character from the uncompressed line
+ return pending.charAt(countOut++);
+ }
+
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void close() throws IOException {
+ reader.close();
+ }
+
+ }
+
+ /** Processor handling differential compression for one numerical data field. */
+ private static class NumericDifferential {
+
+ /** Length of the uncompressed text field. */
+ private final int fieldLength;
+
+ /** Number of decimal places uncompressed text field. */
+ private final int decimalPlaces;
+
+ /** State vector. */
+ private final long[] state;
+
+ /** Number of components in the state vector. */
+ private int nbComponents;
+
+ /** Uncompressed value. */
+ private String uncompressed;
+
+ /** Simple constructor.
+ * @param fieldLength length of the uncompressed text field
+ * @param decimalPlaces number of decimal places uncompressed text field
+ * @param order differential order
+ */
+ NumericDifferential(final int fieldLength, final int decimalPlaces, final int order) {
+ this.fieldLength = fieldLength;
+ this.decimalPlaces = decimalPlaces;
+ this.state = new long[order + 1];
+ this.nbComponents = 0;
+ }
+
+ /** Handle a new compressed value.
+ * @param sequence sequence containing the value to consider
+ */
+ public void accept(final CharSequence sequence) {
+
+ // store the value as the last component of state vector
+ state[nbComponents] = Long.parseLong(sequence.toString());
+
+ // update state vector
+ for (int i = nbComponents; i > 0; --i) {
+ state[i - 1] += state[i];
+ }
+
+ if (++nbComponents == state.length) {
+ // the state vector is full
+ --nbComponents;
+ }
+
+ // output uncompressed value
+ final String unscaled = Long.toString(FastMath.abs(state[0]));
+ final int length = unscaled.length();
+ final int digits = FastMath.max(length, decimalPlaces);
+ final int padding = fieldLength - (digits + (state[0] < 0 ? 2 : 1));
+ final StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < padding; ++i) {
+ builder.append(' ');
+ }
+ if (state[0] < 0) {
+ builder.append('-');
+ }
+ if (length > decimalPlaces) {
+ builder.append(unscaled, 0, length - decimalPlaces);
+ }
+ builder.append('.');
+ for (int i = decimalPlaces; i > 0; --i) {
+ builder.append(i > length ? '0' : unscaled.charAt(length - i));
+ }
+
+ uncompressed = builder.toString();
+
+ }
+
+ /** Get a string representation of the uncompressed value.
+ * @return string representation of the uncompressed value
+ */
+ public String getUncompressed() {
+ return uncompressed;
+ }
+
+ }
+
+ /** Processor handling text compression for one text data field. */
+ private static class TextDifferential {
+
+ /** Buffer holding the current state. */
+ private CharBuffer state;
+
+ /** Simple constructor.
+ * @param fieldLength length of the uncompressed text field
+ */
+ TextDifferential(final int fieldLength) {
+ this.state = CharBuffer.allocate(fieldLength);
+ for (int i = 0; i < fieldLength; ++i) {
+ state.put(i, ' ');
+ }
+ }
+
+ /** Handle a new compressed value.
+ * @param sequence sequence containing the value to consider
+ */
+ public void accept(final CharSequence sequence) {
+
+ // update state
+ final int length = FastMath.min(state.capacity(), sequence.length());
+ for (int i = 0; i < length; ++i) {
+ final char c = sequence.charAt(i);
+ if (c == '&') {
+ // update state with disappearing character
+ state.put(i, ' ');
+ } else if (c != ' ') {
+ // update state with changed character
+ state.put(i, c);
+ }
+ }
+
+ }
+
+ /** Get a string representation of the uncompressed value.
+ * @return string representation of the uncompressed value
+ */
+ public String getUncompressed() {
+ return state.toString();
+ }
+
+ }
+
+ /** Container for combined observations and flags. */
+ private static class CombinedDifferentials {
+
+ /** Observation differentials. */
+ private NumericDifferential[] observations;
+
+ /** Flags differential. */
+ private TextDifferential flags;
+
+ /** Simple constructor.
+ * Build an empty container.
+ * @param nbObs number of observations
+ */
+ CombinedDifferentials(final int nbObs) {
+ this.observations = new NumericDifferential[nbObs];
+ this.flags = new TextDifferential(2 * nbObs);
+ }
+
+ }
+
+ /** Base class for parsing compact RINEX format. */
+ private abstract static class CompactRinexFormat {
+
+ /** Index of label in data lines. */
+ private static final int LABEL_START = 60;
+
+ /** Label for compact Rinex version. */
+ private static final String CRINEX_VERSION_TYPE = "CRINEX VERS / TYPE";
+
+ /** Label for compact Rinex program. */
+ private static final String CRINEX_PROG_DATE = "CRINEX PROG / DATE";
+
+ /** Label for number of satellites. */
+ private static final String NB_OF_SATELLITES = "# OF SATELLITES";
+
+ /** Label for end of header. */
+ private static final String END_OF_HEADER = "END OF HEADER";
+
+ /** Default number of satellites (used if not present in the file). */
+ private static final int DEFAULT_NB_SAT = 500;
+
+ /** File name. */
+ private final String name;
+
+ /** Line-oriented input. */
+ private final BufferedReader reader;
+
+ /** Current line number. */
+ private int lineNumber;
+
+ /** Maximum number of observations for one satellite. */
+ private final Map maxObs;
+
+ /** Number of satellites. */
+ private int nbSat;
+
+ /** Indicator for current section type. */
+ private Section section;
+
+ /** Satellites observed at current epoch. */
+ private List satellites;
+
+ /** Differential engine for epoch. */
+ private TextDifferential epochDifferential;
+
+ /** Receiver clock offset differential. */
+ private NumericDifferential clockDifferential;
+
+ /** Differential engine for satellites list. */
+ private TextDifferential satListDifferential;
+
+ /** Differential engines for each satellite. */
+ private Map differentials;
+
+ /** Simple constructor.
+ * @param name file name
+ * @param reader line-oriented input
+ */
+ protected CompactRinexFormat(final String name, final BufferedReader reader) {
+ this.name = name;
+ this.reader = reader;
+ this.maxObs = new HashMap<>();
+ for (final SatelliteSystem system : SatelliteSystem.values()) {
+ maxObs.put(system, 0);
+ }
+ this.nbSat = DEFAULT_NB_SAT;
+ this.section = Section.HEADER;
+ }
+
+ /** Uncompress a section.
+ * @param firstLine first line of the section
+ * @return uncompressed section (contains several lines)
+ * @exception IOException if we cannot read lines from underlying stream
+ */
+ public String uncompressSection(final String firstLine)
+ throws IOException {
+ final String uncompressed;
+ switch (section) {
+
+ case HEADER : {
+ // header lines
+ final StringBuilder builder = new StringBuilder();
+ String line = firstLine;
+ lineNumber = 3; // there are 2 CRINEX lines before the RINEX header line
+ while (section == Section.HEADER) {
+ if (builder.length() > 0) {
+ builder.append('\n');
+ line = readLine();
+ }
+ builder.append(parseHeaderLine(line));
+ trimTrailingSpaces(builder);
+ }
+ uncompressed = builder.toString();
+ section = Section.EPOCH;
+ break;
+ }
+
+ case EPOCH : {
+ // epoch and receiver clock offset lines
+ ++lineNumber; // the caller has read one epoch line
+ uncompressed = parseEpochAndClockLines(firstLine, readLine().trim());
+ section = Section.OBSERVATION;
+ break;
+ }
+
+ default : {
+ // observation lines
+ final String[] lines = new String[satellites.size()];
+ ++lineNumber; // the caller has read one observation line
+ lines[0] = firstLine;
+ for (int i = 1; i < lines.length; ++i) {
+ lines[i] = readLine();
+ }
+ uncompressed = parseObservationLines(lines);
+ section = Section.EPOCH;
+ }
+
+ }
+
+ return uncompressed;
+
+ }
+
+ /** Parse a header line.
+ * @param line header line
+ * @return uncompressed line
+ */
+ public String parseHeaderLine(final String line) {
+
+ if (isHeaderLine(NB_OF_SATELLITES, line)) {
+ // number of satellites
+ nbSat = parseInt(line, 0, 6);
+ } else if (isHeaderLine(END_OF_HEADER, line)) {
+ // we have reached end of header, prepare parsing of data records
+ section = Section.EPOCH;
+ }
+
+ // within header, lines are simply copied
+ return line;
+
+ }
+
+ /** Parse epoch and receiver clock offset lines.
+ * @param epochLine epoch line
+ * @param clockLine receiver clock offset line
+ * @return uncompressed line
+ * @exception IOException if we cannot read additional special events lines
+ */
+ public abstract String parseEpochAndClockLines(String epochLine, String clockLine)
+ throws IOException;
+
+ /** Parse epoch and receiver clock offset lines.
+ * @param builder builder that may used to copy special event lines
+ * @param epochStart start of the epoch field
+ * @param epochLength length of epoch field
+ * @param eventStart start of the special events field
+ * @param nbSatStart start of the number of satellites field
+ * @param satListStart start of the satellites list
+ * @param clockLength length of receiver clock field
+ * @param clockDecimalPlaces number of decimal places for receiver clock offset
+ * @param epochLine epoch line
+ * @param clockLine receiver clock offset line
+ * @param resetChar character indicating differentials reset
+ * @exception IOException if we cannot read additional special events lines
+ */
+ protected void doParseEpochAndClockLines(final StringBuilder builder,
+ final int epochStart, final int epochLength,
+ final int eventStart, final int nbSatStart, final int satListStart,
+ final int clockLength, final int clockDecimalPlaces,
+ final String epochLine,
+ final String clockLine, final char resetChar)
+ throws IOException {
+
+ boolean loop = true;
+ String loopEpochLine = epochLine;
+ String loopClockLine = clockLine;
+ while (loop) {
+
+ // check if differentials should be reset
+ if (epochDifferential == null || loopEpochLine.charAt(0) == resetChar) {
+ epochDifferential = new TextDifferential(epochLength);
+ satListDifferential = new TextDifferential(nbSat * 3);
+ differentials = new HashMap<>();
+ }
+
+ // check for special events
+ epochDifferential.accept(loopEpochLine.subSequence(epochStart,
+ FastMath.min(loopEpochLine.length(), epochStart + epochLength)));
+ if (parseInt(epochDifferential.getUncompressed(), eventStart, 1) > 1) {
+ // this was not really the epoch, but rather a special event
+ // we just copy the lines and skip to real epoch and clock lines
+ builder.append(epochDifferential.getUncompressed());
+ trimTrailingSpaces(builder);
+ builder.append('\n');
+ final int skippedLines = parseInt(epochDifferential.getUncompressed(), nbSatStart, 3);
+ for (int i = 0; i < skippedLines; ++i) {
+ builder.append(loopClockLine);
+ trimTrailingSpaces(builder);
+ builder.append('\n');
+ loopClockLine = readLine();
+ }
+
+ // the epoch and clock are in the next lines
+ loopEpochLine = loopClockLine;
+ loopClockLine = readLine();
+ loop = true;
+
+ } else {
+ loop = false;
+ final int n = parseInt(epochDifferential.getUncompressed(), nbSatStart, 3);
+ satellites = new ArrayList<>(n);
+ if (satListStart < loopEpochLine.length()) {
+ satListDifferential.accept(loopEpochLine.subSequence(satListStart, loopEpochLine.length()));
+ }
+ final String satListPart = satListDifferential.getUncompressed();
+ for (int i = 0; i < n; ++i) {
+ satellites.add(satListPart.subSequence(i * 3, (i + 1) * 3));
+ }
+
+ // parse clock offset
+ if (!loopClockLine.isEmpty()) {
+ if (loopClockLine.length() > 2 && loopClockLine.charAt(1) == '&') {
+ clockDifferential = new NumericDifferential(clockLength, clockDecimalPlaces, parseInt(loopClockLine, 0, 1));
+ clockDifferential.accept(loopClockLine.subSequence(2, loopClockLine.length()));
+ } else if (clockDifferential == null) {
+ throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
+ lineNumber, name, loopClockLine);
+ } else {
+ clockDifferential.accept(loopClockLine);
+ }
+ }
+ }
+ }
+
+ }
+
+ /** Get the uncompressed epoch part.
+ * @return uncompressed epoch part
+ */
+ protected String getEpochPart() {
+ return epochDifferential.getUncompressed();
+ }
+
+ /** Get the uncompressed clock part.
+ * @return uncompressed clock part
+ */
+ protected String getClockPart() {
+ return clockDifferential == null ? "" : clockDifferential.getUncompressed();
+ }
+
+ /** Get the satellites for current observations.
+ * @return satellites for current observation
+ */
+ protected List getSatellites() {
+ return satellites;
+ }
+
+ /** Get the combined differentials for one satellite.
+ * @param sat satellite id
+ * @return observationDifferentials
+ */
+ protected CombinedDifferentials getCombinedDifferentials(final CharSequence sat) {
+ return differentials.get(sat);
+ }
+
+ /** Parse observation lines.
+ * @param observationLines observation lines
+ * @return uncompressed lines
+ */
+ public abstract String parseObservationLines(String[] observationLines);
+
+ /** Parse observation lines.
+ * @param dataLength length of data fields
+ * @param dataDecimalPlaces number of decimal places for data fields
+ * @param observationLines observation lines
+ */
+ protected void doParseObservationLines(final int dataLength, final int dataDecimalPlaces,
+ final String[] observationLines) {
+
+ for (int i = 0; i < observationLines.length; ++i) {
+
+ final CharSequence line = observationLines[i];
+
+ // get the differentials associated with this observations line
+ final CharSequence sat = satellites.get(i);
+ CombinedDifferentials satDiffs = differentials.get(sat);
+ if (satDiffs == null) {
+ final SatelliteSystem system = SatelliteSystem.parseSatelliteSystem(sat.subSequence(0, 1).toString());
+ satDiffs = new CombinedDifferentials(maxObs.get(system));
+ differentials.put(sat, satDiffs);
+ }
+
+ // parse observations
+ int k = 0;
+ for (int j = 0; j < satDiffs.observations.length; ++j) {
+
+ if (k >= line.length() || line.charAt(k) == ' ') {
+ // the data field is missing
+ satDiffs.observations[j] = null;
+ } else {
+ // the data field is present
+
+ if (k + 1 < line.length() &&
+ Character.isDigit(line.charAt(k)) &&
+ line.charAt(k + 1) == '&') {
+ // reinitialize differentials
+ satDiffs.observations[j] = new NumericDifferential(dataLength, dataDecimalPlaces,
+ Character.digit(line.charAt(k), 10));
+ k += 2;
+ }
+
+ // extract the compressed differenced value
+ final int start = k;
+ while (k < line.length() && line.charAt(k) != ' ') {
+ ++k;
+ }
+ try {
+ satDiffs.observations[j].accept(line.subSequence(start, k));
+ } catch (NumberFormatException nfe) {
+ throw new OrekitException(nfe,
+ OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
+ lineNumber + i - (observationLines.length - 1),
+ name, observationLines[i]);
+ }
+
+ }
+
+ // skip blank separator
+ ++k;
+
+ }
+
+ if (k < line.length()) {
+ satDiffs.flags.accept(line.subSequence(k, line.length()));
+ }
+
+ }
+
+ }
+
+ /** Check if a line corresponds to a header.
+ * @param label header label
+ * @param line header line
+ * @return true if line corresponds to header
+ */
+ protected boolean isHeaderLine(final String label, final String line) {
+ return label.equals(parseString(line, LABEL_START, label.length()));
+ }
+
+ /** Update the max number of observations.
+ * @param system satellite system
+ * @param nbObs number of observations
+ */
+ protected void updateMaxObs(final SatelliteSystem system, final int nbObs) {
+ maxObs.put(system, FastMath.max(maxObs.get(system), nbObs));
+ }
+
+ /** Read a new line.
+ * @return line read
+ * @exception IOException if a read error occurs
+ */
+ private String readLine()
+ throws IOException {
+ final String line = reader.readLine();
+ if (line == null) {
+ throw new OrekitException(OrekitMessages.UNEXPECTED_END_OF_FILE, name);
+ }
+ lineNumber++;
+ return line;
+ }
+
+ /** Get the rinex format corresponding to this compact rinex format.
+ * @param name file name
+ * @param reader line-oriented input
+ * @return rinex format associated with this compact rinex format
+ * @exception IOException if first lines cannot be read
+ */
+ public static CompactRinexFormat getFormat(final String name, final BufferedReader reader)
+ throws IOException {
+
+ // read the first two lines of the file
+ final String line1 = reader.readLine();
+ final String line2 = reader.readLine();
+ if (line1 == null || line2 == null) {
+ throw new OrekitException(OrekitMessages.NOT_A_SUPPORTED_HATANAKA_COMPRESSED_FILE, name);
+ }
+
+ // extract format version
+ final int cVersion100 = (int) FastMath.rint(100 * parseDouble(line1, 0, 9));
+ if ((cVersion100 != 100) && (cVersion100 != 300)) {
+ throw new OrekitException(OrekitMessages.UNSUPPORTED_FILE_FORMAT, name);
+ }
+ if (!CRINEX_VERSION_TYPE.equals(parseString(line1, LABEL_START, CRINEX_VERSION_TYPE.length()))) {
+ throw new OrekitException(OrekitMessages.NOT_A_SUPPORTED_HATANAKA_COMPRESSED_FILE, name);
+ }
+ if (!CRINEX_PROG_DATE.equals(parseString(line2, LABEL_START, CRINEX_PROG_DATE.length()))) {
+ throw new OrekitException(OrekitMessages.NOT_A_SUPPORTED_HATANAKA_COMPRESSED_FILE, name);
+ }
+
+ // build the appropriate parser
+ return cVersion100 < 300 ? new CompactRinex1(name, reader) : new CompactRinex3(name, reader);
+
+ }
+
+ /** Extract a string from a line.
+ * @param line to parse
+ * @param start start index of the string
+ * @param length length of the string
+ * @return parsed string
+ */
+ public static String parseString(final String line, final int start, final int length) {
+ if (line.length() > start) {
+ return line.substring(start, FastMath.min(line.length(), start + length)).trim();
+ } else {
+ return null;
+ }
+ }
+
+ /** Extract an integer from a line.
+ * @param line to parse
+ * @param start start index of the integer
+ * @param length length of the integer
+ * @return parsed integer
+ */
+ public static int parseInt(final String line, final int start, final int length) {
+ if (line.length() > start && !parseString(line, start, length).isEmpty()) {
+ return Integer.parseInt(parseString(line, start, length));
+ } else {
+ return 0;
+ }
+ }
+
+ /** Extract a double from a line.
+ * @param line to parse
+ * @param start start index of the real
+ * @param length length of the real
+ * @return parsed real, or {@code Double.NaN} if field was empty
+ */
+ public static double parseDouble(final String line, final int start, final int length) {
+ if (line.length() > start && !parseString(line, start, length).isEmpty()) {
+ return Double.parseDouble(parseString(line, start, length));
+ } else {
+ return Double.NaN;
+ }
+ }
+
+ /** Trim trailing spaces in a builder.
+ * @param builder builder to trim
+ */
+ public static void trimTrailingSpaces(final StringBuilder builder) {
+ for (int i = builder.length() - 1; i >= 0 && builder.charAt(i) == ' '; --i) {
+ builder.deleteCharAt(i);
+ }
+ }
+
+ /** Enumerate for parsing sections. */
+ private enum Section {
+
+ /** Header section. */
+ HEADER,
+
+ /** Epoch and receiver clock offset section. */
+ EPOCH,
+
+ /** Observation section. */
+ OBSERVATION;
+
+ }
+
+ }
+
+ /** Compact RINEX 1 format (for RINEX 2.x). */
+ private static class CompactRinex1 extends CompactRinexFormat {
+
+ /** Label for number of observations. */
+ private static final String NB_TYPES_OF_OBSERV = "# / TYPES OF OBSERV";
+
+ /** Start of epoch field. */
+ private static final int EPOCH_START = 0;
+
+ /** Length of epoch field. */
+ private static final int EPOCH_LENGTH = 32;
+
+ /** Start of events flag. */
+ private static final int EVENT_START = EPOCH_START + EPOCH_LENGTH - 4;
+
+ /** Start of number of satellites field. */
+ private static final int NB_SAT_START = EPOCH_START + EPOCH_LENGTH - 3;
+
+ /** Start of satellites list field. */
+ private static final int SAT_LIST_START = EPOCH_START + EPOCH_LENGTH;
+
+ /** Length of satellites list field. */
+ private static final int SAT_LIST_LENGTH = 36;
+
+ /** Maximum number of satellites per epoch line. */
+ private static final int MAX_SAT_EPOCH_LINE = 12;
+
+ /** Start of receiver clock field. */
+ private static final int CLOCK_START = SAT_LIST_START + SAT_LIST_LENGTH;
+
+ /** Length of receiver clock field. */
+ private static final int CLOCK_LENGTH = 12;
+
+ /** Number of decimal places for receiver clock offset. */
+ private static final int CLOCK_DECIMAL_PLACES = 9;
+
+ /** Length of a data field. */
+ private static final int DATA_LENGTH = 14;
+
+ /** Number of decimal places for data fields. */
+ private static final int DATA_DECIMAL_PLACES = 3;
+
+ /** Simple constructor.
+ * @param name file name
+ * @param reader line-oriented input
+ */
+ CompactRinex1(final String name, final BufferedReader reader) {
+ super(name, reader);
+ }
+
+ @Override
+ /** {@inheritDoc} */
+ public String parseHeaderLine(final String line) {
+ if (isHeaderLine(NB_TYPES_OF_OBSERV, line)) {
+ for (final SatelliteSystem system : SatelliteSystem.values()) {
+ updateMaxObs(system, parseInt(line, 0, 6));
+ }
+ return line;
+ } else {
+ return super.parseHeaderLine(line);
+ }
+ }
+
+ @Override
+ /** {@inheritDoc} */
+ public String parseEpochAndClockLines(final String epochLine, final String clockLine)
+ throws IOException {
+
+ final StringBuilder builder = new StringBuilder();
+ doParseEpochAndClockLines(builder,
+ EPOCH_START, EPOCH_LENGTH, EVENT_START, NB_SAT_START, SAT_LIST_START,
+ CLOCK_LENGTH, CLOCK_DECIMAL_PLACES, epochLine,
+ clockLine, '&');
+
+ // build uncompressed lines, taking care of clock being put
+ // back in line 1 and satellites after 12th put in continuation lines
+ final List satellites = getSatellites();
+ builder.append(getEpochPart());
+ int iSat = 0;
+ while (iSat < FastMath.min(satellites.size(), MAX_SAT_EPOCH_LINE)) {
+ builder.append(satellites.get(iSat++));
+ }
+ if (!getClockPart().isEmpty()) {
+ while (builder.length() < CLOCK_START) {
+ builder.append(' ');
+ }
+ builder.append(getClockPart());
+ }
+
+ while (iSat < satellites.size()) {
+ // add a continuation line
+ trimTrailingSpaces(builder);
+ builder.append('\n');
+ for (int k = 0; k < SAT_LIST_START; ++k) {
+ builder.append(' ');
+ }
+ final int iSatStart = iSat;
+ while (iSat < FastMath.min(satellites.size(), iSatStart + MAX_SAT_EPOCH_LINE)) {
+ builder.append(satellites.get(iSat++));
+ }
+ }
+ trimTrailingSpaces(builder);
+ return builder.toString();
+
+ }
+
+ @Override
+ /** {@inheritDoc} */
+ public String parseObservationLines(final String[] observationLines) {
+
+ // parse the observation lines
+ doParseObservationLines(DATA_LENGTH, DATA_DECIMAL_PLACES, observationLines);
+
+ // build uncompressed lines
+ final StringBuilder builder = new StringBuilder();
+ for (final CharSequence sat : getSatellites()) {
+ if (builder.length() > 0) {
+ trimTrailingSpaces(builder);
+ builder.append('\n');
+ }
+ final CombinedDifferentials cd = getCombinedDifferentials(sat);
+ final String flags = cd.flags.getUncompressed();
+ for (int i = 0; i < cd.observations.length; ++i) {
+ if (i > 0 && i % 5 == 0) {
+ trimTrailingSpaces(builder);
+ builder.append('\n');
+ }
+ if (cd.observations[i] == null) {
+ // missing observation
+ for (int j = 0; j < DATA_LENGTH + 2; ++j) {
+ builder.append(' ');
+ }
+ } else {
+ builder.append(cd.observations[i].getUncompressed());
+ if (2 * i < flags.length()) {
+ builder.append(flags.charAt(2 * i));
+ }
+ if (2 * i + 1 < flags.length()) {
+ builder.append(flags.charAt(2 * i + 1));
+ }
+ }
+ }
+ }
+ trimTrailingSpaces(builder);
+ return builder.toString();
+
+ }
+
+ }
+
+ /** Compact RINEX 3 format (for RINEX 3.x). */
+ private static class CompactRinex3 extends CompactRinexFormat {
+
+ /** Label for number of observation types. */
+ private static final String SYS_NB_OBS_TYPES = "SYS / # / OBS TYPES";
+
+ /** Start of epoch field. */
+ private static final int EPOCH_START = 0;
+
+ /** Length of epoch field. */
+ private static final int EPOCH_LENGTH = 41;
+
+ /** Start of receiver clock field. */
+ private static final int CLOCK_START = EPOCH_START + EPOCH_LENGTH;
+
+ /** Length of receiver clock field. */
+ private static final int CLOCK_LENGTH = 15;
+
+ /** Number of decimal places for receiver clock offset. */
+ private static final int CLOCK_DECIMAL_PLACES = 12;
+
+ /** Start of events flag. */
+ private static final int EVENT_START = EPOCH_START + EPOCH_LENGTH - 10;
+
+ /** Start of number of satellites field. */
+ private static final int NB_SAT_START = EPOCH_START + EPOCH_LENGTH - 9;
+
+ /** Start of satellites list field (only in the compact rinex). */
+ private static final int SAT_LIST_START = EPOCH_START + EPOCH_LENGTH;
+
+ /** Length of a data field. */
+ private static final int DATA_LENGTH = 14;
+
+ /** Number of decimal places for data fields. */
+ private static final int DATA_DECIMAL_PLACES = 3;
+
+ /** Simple constructor.
+ * @param name file name
+ * @param reader line-oriented input
+ */
+ CompactRinex3(final String name, final BufferedReader reader) {
+ super(name, reader);
+ }
+
+ @Override
+ /** {@inheritDoc} */
+ public String parseHeaderLine(final String line) {
+ if (isHeaderLine(SYS_NB_OBS_TYPES, line)) {
+ if (line.charAt(0) != ' ') {
+ // it is the first line of an observation types description
+ // (continuation lines are ignored here)
+ updateMaxObs(SatelliteSystem.parseSatelliteSystem(parseString(line, 0, 1)),
+ parseInt(line, 1, 5));
+ }
+ return line;
+ } else {
+ return super.parseHeaderLine(line);
+ }
+ }
+
+ @Override
+ /** {@inheritDoc} */
+ public String parseEpochAndClockLines(final String epochLine, final String clockLine)
+ throws IOException {
+
+ final StringBuilder builder = new StringBuilder();
+ doParseEpochAndClockLines(builder,
+ EPOCH_START, EPOCH_LENGTH, EVENT_START, NB_SAT_START, SAT_LIST_START,
+ CLOCK_LENGTH, CLOCK_DECIMAL_PLACES, epochLine,
+ clockLine, '>');
+
+ // build uncompressed line
+ builder.append(getEpochPart());
+ if (!getClockPart().isEmpty()) {
+ while (builder.length() < CLOCK_START) {
+ builder.append(' ');
+ }
+ builder.append(getClockPart());
+ }
+
+ trimTrailingSpaces(builder);
+ return builder.toString();
+
+ }
+
+ @Override
+ /** {@inheritDoc} */
+ public String parseObservationLines(final String[] observationLines) {
+
+ // parse the observation lines
+ doParseObservationLines(DATA_LENGTH, DATA_DECIMAL_PLACES, observationLines);
+
+ // build uncompressed lines
+ final StringBuilder builder = new StringBuilder();
+ for (final CharSequence sat : getSatellites()) {
+ if (builder.length() > 0) {
+ trimTrailingSpaces(builder);
+ builder.append('\n');
+ }
+ builder.append(sat);
+ final CombinedDifferentials cd = getCombinedDifferentials(sat);
+ final String flags = cd.flags.getUncompressed();
+ for (int i = 0; i < cd.observations.length; ++i) {
+ if (cd.observations[i] == null) {
+ // missing observation
+ for (int j = 0; j < DATA_LENGTH + 2; ++j) {
+ builder.append(' ');
+ }
+ } else {
+ builder.append(cd.observations[i].getUncompressed());
+ if (2 * i < flags.length()) {
+ builder.append(flags.charAt(2 * i));
+ }
+ if (2 * i + 1 < flags.length()) {
+ builder.append(flags.charAt(2 * i + 1));
+ }
+ }
+ }
+ }
+ trimTrailingSpaces(builder);
+ return builder.toString();
+
+ }
+
+ }
+
+}
diff --git a/src/main/java/org/orekit/gnss/IRNSSAlmanac.java b/src/main/java/org/orekit/gnss/IRNSSAlmanac.java
new file mode 100644
index 0000000000000000000000000000000000000000..7887bdff40ff566ef2bb58b4f568f5af7b47e6e3
--- /dev/null
+++ b/src/main/java/org/orekit/gnss/IRNSSAlmanac.java
@@ -0,0 +1,212 @@
+/* Copyright 2002-2019 CS Systèmes d'Information
+ * Licensed to CS Systèmes d'Information (CS) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.gnss;
+
+import org.hipparchus.util.FastMath;
+import org.orekit.propagation.analytical.gnss.IRNSSOrbitalElements;
+import org.orekit.time.AbsoluteDate;
+import org.orekit.time.GNSSDate;
+
+/**
+ * Class for IRNSS almanac.
+ *
+ * @see "Indian Regiona Navigation Satellite System, Signal In Space ICD
+ * for standard positioning service, version 1.1 - Table 28"
+ *
+ * @author Bryan Cazabonne
+ * @since 10.1
+ *
+ */
+public class IRNSSAlmanac implements IRNSSOrbitalElements {
+
+ /** PRN number. */
+ private final int prn;
+
+ /** IRNSS week. */
+ private final int week;
+
+ /** Time of applicability. */
+ private final double toa;
+
+ /** Semi-major axis. */
+ private final double sma;
+
+ /** Eccentricity. */
+ private final double ecc;
+
+ /** Inclination. */
+ private final double inc;
+
+ /** Longitude of Orbital Plane. */
+ private final double om0;
+
+ /** Rate of Right Ascension. */
+ private final double dom;
+
+ /** Argument of perigee. */
+ private final double aop;
+
+ /** Mean anomaly. */
+ private final double anom;
+
+ /** Zeroth order clock correction. */
+ private final double af0;
+
+ /** First order clock correction. */
+ private final double af1;
+
+ /**
+ * Constructor.
+ *
+ * @param prn the PRN number
+ * @param week the GPS week
+ * @param toa the Time of Applicability
+ * @param sqa the Square Root of Semi-Major Axis (m^1/2)
+ * @param ecc the eccentricity
+ * @param inc the inclination (rad)
+ * @param om0 the geographic longitude of the orbital plane at the weekly epoch (rad)
+ * @param dom the Rate of Right Ascension (rad/s)
+ * @param aop the Argument of Perigee (rad)
+ * @param anom the Mean Anomaly (rad)
+ * @param af0 the Zeroth Order Clock Correction (s)
+ * @param af1 the First Order Clock Correction (s/s)
+ */
+ public IRNSSAlmanac(final int prn, final int week, final double toa,
+ final double sqa, final double ecc, final double inc,
+ final double om0, final double dom, final double aop,
+ final double anom, final double af0, final double af1) {
+ this.prn = prn;
+ this.week = week;
+ this.toa = toa;
+ this.sma = sqa * sqa;
+ this.ecc = ecc;
+ this.inc = inc;
+ this.om0 = om0;
+ this.dom = dom;
+ this.aop = aop;
+ this.anom = anom;
+ this.af0 = af0;
+ this.af1 = af1;
+ }
+
+ @Override
+ public AbsoluteDate getDate() {
+ return new GNSSDate(week, toa * 1000., SatelliteSystem.IRNSS).getDate();
+ }
+
+ @Override
+ public int getPRN() {
+ return prn;
+ }
+
+ @Override
+ public int getWeek() {
+ return week;
+ }
+
+ @Override
+ public double getTime() {
+ return toa;
+ }
+
+ @Override
+ public double getSma() {
+ return sma;
+ }
+
+ @Override
+ public double getMeanMotion() {
+ final double absA = FastMath.abs(sma);
+ return FastMath.sqrt(IRNSS_MU / absA) / absA;
+ }
+
+ @Override
+ public double getE() {
+ return ecc;
+ }
+
+ @Override
+ public double getI0() {
+ return inc;
+ }
+
+ @Override
+ public double getIDot() {
+ return 0;
+ }
+
+ @Override
+ public double getOmega0() {
+ return om0;
+ }
+
+ @Override
+ public double getOmegaDot() {
+ return dom;
+ }
+
+ @Override
+ public double getPa() {
+ return aop;
+ }
+
+ @Override
+ public double getM0() {
+ return anom;
+ }
+
+ @Override
+ public double getCuc() {
+ return 0;
+ }
+
+ @Override
+ public double getCus() {
+ return 0;
+ }
+
+ @Override
+ public double getCrc() {
+ return 0;
+ }
+
+ @Override
+ public double getCrs() {
+ return 0;
+ }
+
+ @Override
+ public double getCic() {
+ return 0;
+ }
+
+ @Override
+ public double getCis() {
+ return 0;
+ }
+
+ @Override
+ public double getAf0() {
+ return af0;
+ }
+
+ @Override
+ public double getAf1() {
+ return af1;
+ }
+
+}
diff --git a/src/main/java/org/orekit/gnss/MeasurementType.java b/src/main/java/org/orekit/gnss/MeasurementType.java
index 8cd0f4a22f0da4a419fc6396b74f213f136d7f62..8bddafe73a02bbf92628998e785224b088b35b3c 100644
--- a/src/main/java/org/orekit/gnss/MeasurementType.java
+++ b/src/main/java/org/orekit/gnss/MeasurementType.java
@@ -34,6 +34,9 @@ public enum MeasurementType {
DOPPLER,
/** Signal-strength measurement. */
- SIGNAL_STRENGTH;
+ SIGNAL_STRENGTH,
+
+ /** Combined pseudo-range carrier-phase measurement. */
+ COMBINED_RANGE_PHASE;
}
diff --git a/src/main/java/org/orekit/gnss/ObservationType.java b/src/main/java/org/orekit/gnss/ObservationType.java
index 7d6fb036e64a4213504ed55f3f83121d3e00ba0a..8e56f1889ec60fc78195fe787d072947860f78af 100644
--- a/src/main/java/org/orekit/gnss/ObservationType.java
+++ b/src/main/java/org/orekit/gnss/ObservationType.java
@@ -30,776 +30,865 @@ import java.util.Map;
public enum ObservationType {
/** Pseudorange GPS L1 / GLONASS G1 / Galileo E2-L1-E1 / SBAS L1 for Rinex2. */
- C1(MeasurementType.PSEUDO_RANGE, Frequency.G01, Frequency.R01, Frequency.E01, Frequency.S01),
+ C1(MeasurementType.PSEUDO_RANGE, SignalCode.C, Frequency.G01, Frequency.R01, Frequency.E01, Frequency.S01),
/** Pseudorange GPS L2 / GLONASS G2 / Beidou B02 for Rinex2. */
- C2(MeasurementType.PSEUDO_RANGE, Frequency.G02, Frequency.R02, Frequency.B02),
+ C2(MeasurementType.PSEUDO_RANGE, SignalCode.C, Frequency.G02, Frequency.R02, Frequency.B02),
/** Pseudorange GPS L5 / Galileo E5a / SBAS L5 for Rinex2. */
- C5(MeasurementType.PSEUDO_RANGE, Frequency.G05, Frequency.E05, Frequency.S05),
+ C5(MeasurementType.PSEUDO_RANGE, SignalCode.C, Frequency.G05, Frequency.E05, Frequency.S05),
/** Pseudorange Galileo E6 / Beidou B03 for Rinex2. */
- C6(MeasurementType.PSEUDO_RANGE, Frequency.E06, Frequency.B03),
+ C6(MeasurementType.PSEUDO_RANGE, SignalCode.C, Frequency.E06, Frequency.B03),
/** Pseudorange Galileo E5b / Beidou B02 for Rinex2. */
- C7(MeasurementType.PSEUDO_RANGE, Frequency.E07, Frequency.B02),
+ C7(MeasurementType.PSEUDO_RANGE, SignalCode.C, Frequency.E07, Frequency.B02),
/** Pseudorange Galileo E5a+b for Rinex2. */
- C8(MeasurementType.PSEUDO_RANGE, Frequency.E08),
+ C8(MeasurementType.PSEUDO_RANGE, SignalCode.C, Frequency.E08),
/** Pseudorange GPS L1 / GLONASS G1 for Rinex2. */
- P1(MeasurementType.PSEUDO_RANGE, Frequency.G01, Frequency.R01),
+ P1(MeasurementType.PSEUDO_RANGE, SignalCode.P, Frequency.G01, Frequency.R01),
/** Pseudorange GPS L2 / GLONASS G2 for Rinex2. */
- P2(MeasurementType.PSEUDO_RANGE, Frequency.G02, Frequency.R02),
+ P2(MeasurementType.PSEUDO_RANGE, SignalCode.P, Frequency.G02, Frequency.R02),
/** Carrier-phase GPS L1 / GLONASS G1 / Galileo E2-L1-E1 / SBAS L1 for Rinex2. */
- L1(MeasurementType.CARRIER_PHASE, Frequency.G01, Frequency.R01, Frequency.E01, Frequency.S01),
+ L1(MeasurementType.CARRIER_PHASE, SignalCode.P, Frequency.G01, Frequency.R01, Frequency.E01, Frequency.S01),
/** Carrier-phase GPS L2 / GLONASS G2 / Beidou B02 for Rinex2. */
- L2(MeasurementType.CARRIER_PHASE, Frequency.G02, Frequency.R02, Frequency.B02),
+ L2(MeasurementType.CARRIER_PHASE, SignalCode.P, Frequency.G02, Frequency.R02, Frequency.B02),
/** Carrier-phase GPS L5 / Galileo E5a / SBAS L5 for Rinex2. */
- L5(MeasurementType.CARRIER_PHASE, Frequency.G05, Frequency.E05, Frequency.S05),
+ L5(MeasurementType.CARRIER_PHASE, SignalCode.P, Frequency.G05, Frequency.E05, Frequency.S05),
/** Carrier-phase Galileo E6 / Beidou B03 for Rinex2. */
- L6(MeasurementType.CARRIER_PHASE, Frequency.E06, Frequency.C07),
+ L6(MeasurementType.CARRIER_PHASE, SignalCode.P, Frequency.E06, Frequency.C07),
/** Carrier-phase Galileo E5b / Beidou B02 for Rinex2. */
- L7(MeasurementType.CARRIER_PHASE, Frequency.E07, Frequency.B02),
+ L7(MeasurementType.CARRIER_PHASE, SignalCode.P, Frequency.E07, Frequency.B02),
/** Carrier-phase Galileo E5a+b for Rinex2. */
- L8(MeasurementType.CARRIER_PHASE, Frequency.E08),
+ L8(MeasurementType.CARRIER_PHASE, SignalCode.P, Frequency.E08),
/** Carrier-phase GPS L1 C/A / GLONASS G1 C/A for Rinex2. */
- LA(MeasurementType.CARRIER_PHASE, Frequency.G01, Frequency.R01),
+ LA(MeasurementType.CARRIER_PHASE, SignalCode.C, Frequency.G01, Frequency.R01),
/** Carrier-phase GPS L1C for Rinex2. */
- LB(MeasurementType.CARRIER_PHASE, Frequency.G01),
+ LB(MeasurementType.CARRIER_PHASE, SignalCode.L, Frequency.G01),
/** Carrier-phase GPS L2C for Rinex2. */
- LC(MeasurementType.CARRIER_PHASE, Frequency.G02),
+ LC(MeasurementType.CARRIER_PHASE, SignalCode.L, Frequency.G02),
/** Carrier-phase GLONASS G2 for Rinex2. */
- LD(MeasurementType.CARRIER_PHASE, Frequency.R02),
+ LD(MeasurementType.CARRIER_PHASE, SignalCode.C, Frequency.R02),
/** Doppler GPS L1 / GLONASS G1 / Galileo E2-L1-E1 / SBAS L1 for Rinex2. */
- D1(MeasurementType.DOPPLER, Frequency.G01, Frequency.R01, Frequency.E01, Frequency.S01),
+ D1(MeasurementType.DOPPLER, SignalCode.P, Frequency.G01, Frequency.R01, Frequency.E01, Frequency.S01),
/** Doppler GPS L2 / GLONASS G2 / Beidou BO2 for Rinex2. */
- D2(MeasurementType.DOPPLER, Frequency.G02, Frequency.R02, Frequency.B02),
+ D2(MeasurementType.DOPPLER, SignalCode.P, Frequency.G02, Frequency.R02, Frequency.B02),
/** Doppler GPS L5 / Galileo E5a / SBAS L5 for Rinex2. */
- D5(MeasurementType.DOPPLER, Frequency.G05, Frequency.E05, Frequency.S05),
+ D5(MeasurementType.DOPPLER, SignalCode.P, Frequency.G05, Frequency.E05, Frequency.S05),
/** Doppler Galileo E6 / Beidou B03 for Rinex2. */
- D6(MeasurementType.DOPPLER, Frequency.E06, Frequency.C07),
+ D6(MeasurementType.DOPPLER, SignalCode.P, Frequency.E06, Frequency.C07),
/** Doppler Galileo E5b / Beidou B02 for Rinex2. */
- D7(MeasurementType.DOPPLER, Frequency.E07, Frequency.B02),
+ D7(MeasurementType.DOPPLER, SignalCode.P, Frequency.E07, Frequency.B02),
/** Doppler Galileo E5a+b for Rinex2. */
- D8(MeasurementType.DOPPLER, Frequency.E08),
+ D8(MeasurementType.DOPPLER, SignalCode.P, Frequency.E08),
/** Doppler GPS L1 / GLONASS G1 / Galileo E2-L1-E1 / SBAS L1 for Rinex2. */
- S1(MeasurementType.SIGNAL_STRENGTH, Frequency.G01, Frequency.R01, Frequency.E01, Frequency.S01),
+ S1(MeasurementType.SIGNAL_STRENGTH, SignalCode.P, Frequency.G01, Frequency.R01, Frequency.E01, Frequency.S01),
/** Signal Strength GPS L2 / GLONASS G2 / Beidou B02 for Rinex2. */
- S2(MeasurementType.SIGNAL_STRENGTH, Frequency.G02, Frequency.R02, Frequency.B02),
+ S2(MeasurementType.SIGNAL_STRENGTH, SignalCode.P, Frequency.G02, Frequency.R02, Frequency.B02),
/** Signal Strength GPS L5 / Galileo E5a / SBAS L5 for Rinex2. */
- S5(MeasurementType.SIGNAL_STRENGTH, Frequency.G05, Frequency.E05, Frequency.S05),
+ S5(MeasurementType.SIGNAL_STRENGTH, SignalCode.P, Frequency.G05, Frequency.E05, Frequency.S05),
/** Signal Strength Galileo E6 / Beidou B03 for Rinex2. */
- S6(MeasurementType.SIGNAL_STRENGTH, Frequency.E06, Frequency.C07),
+ S6(MeasurementType.SIGNAL_STRENGTH, SignalCode.P, Frequency.E06, Frequency.C07),
/** Signal Strength Galileo E5b / Beidou B02 for Rinex2. */
- S7(MeasurementType.SIGNAL_STRENGTH, Frequency.E07, Frequency.B02),
+ S7(MeasurementType.SIGNAL_STRENGTH, SignalCode.P, Frequency.E07, Frequency.B02),
/** Signal Strength Galileo E5a+b for Rinex2. */
- S8(MeasurementType.SIGNAL_STRENGTH, Frequency.E08),
+ S8(MeasurementType.SIGNAL_STRENGTH, SignalCode.P, Frequency.E08),
/** Pseudorange Galileo E1 A for Rinex3. */
- C1A(MeasurementType.PSEUDO_RANGE, Frequency.E01),
+ C1A(MeasurementType.PSEUDO_RANGE, SignalCode.A, Frequency.E01),
/** Pseudorange Galileo E1 I/NAV OS/CS/SoL for Rinex3. */
- C1B(MeasurementType.PSEUDO_RANGE, Frequency.E01),
+ C1B(MeasurementType.PSEUDO_RANGE, SignalCode.B, Frequency.E01),
/** Pseudorange GPS L1 C/A / GLONASS G1 C/A / Galileo E1 C / SBAS L1 C/A / QZSS L1 C/A for Rinex3. */
- C1C(MeasurementType.PSEUDO_RANGE, Frequency.G01, Frequency.R01, Frequency.E01, Frequency.S01, Frequency.J01),
+ C1C(MeasurementType.PSEUDO_RANGE, SignalCode.C, Frequency.G01, Frequency.R01, Frequency.E01, Frequency.S01, Frequency.J01),
/** Pseudorange Beidou B1 I for Rinex3.02. */
- C1I(MeasurementType.PSEUDO_RANGE, Frequency.B01),
+ C1I(MeasurementType.PSEUDO_RANGE, SignalCode.I, Frequency.B01),
/** Pseudorange GPS L1 L1C(P) / QZSS L1 L1C(P) for Rinex3. */
- C1L(MeasurementType.PSEUDO_RANGE, Frequency.G01, Frequency.J01),
+ C1L(MeasurementType.PSEUDO_RANGE, SignalCode.L, Frequency.G01, Frequency.J01),
/** Pseudorange GPS L1 M for Rinex3. */
- C1M(MeasurementType.PSEUDO_RANGE, Frequency.G01),
+ C1M(MeasurementType.PSEUDO_RANGE, SignalCode.M, Frequency.G01),
/** Pseudorange GPS L1 P(AS off) / GLONASS G1 P for Rinex3. */
- C1P(MeasurementType.PSEUDO_RANGE, Frequency.G01, Frequency.R01),
+ C1P(MeasurementType.PSEUDO_RANGE, SignalCode.P, Frequency.G01, Frequency.R01),
/** Pseudorange Beidou B1 Q for Rinex3.02. */
- C1Q(MeasurementType.PSEUDO_RANGE, Frequency.B01),
+ C1Q(MeasurementType.PSEUDO_RANGE, SignalCode.Q, Frequency.B01),
/** Pseudorange GPS L1 L1C(D) / QZSS L1 L1C(D) for Rinex3. */
- C1S(MeasurementType.PSEUDO_RANGE, Frequency.G01, Frequency.J01),
+ C1S(MeasurementType.PSEUDO_RANGE, SignalCode.S, Frequency.G01, Frequency.J01),
/** Pseudorange GPS L1 Z-tracking and similar (AS on) for Rinex3. */
- C1W(MeasurementType.PSEUDO_RANGE, Frequency.G01),
+ C1W(MeasurementType.PSEUDO_RANGE, SignalCode.W, Frequency.G01),
/** Pseudorange GPS L1 L1C (D+P) / Galileo E1 B+C / QZSS L1 L1C(D+P) for Rinex3. */
- C1X(MeasurementType.PSEUDO_RANGE, Frequency.G01, Frequency.E01, Frequency.J01),
+ C1X(MeasurementType.PSEUDO_RANGE, SignalCode.X, Frequency.G01, Frequency.E01, Frequency.J01),
/** Pseudorange GPS L1 Y for Rinex3. */
- C1Y(MeasurementType.PSEUDO_RANGE, Frequency.G01),
+ C1Y(MeasurementType.PSEUDO_RANGE, SignalCode.Y, Frequency.G01),
/** Pseudorange Galileo E1 C1Z A+B+C / QZSS L1 L1-SAIF for Rinex3. */
- C1Z(MeasurementType.PSEUDO_RANGE, Frequency.E01, Frequency.J01),
+ C1Z(MeasurementType.PSEUDO_RANGE, SignalCode.Z, Frequency.E01, Frequency.J01),
/** Pseudorange GPS L2 C/A / GLONASS G2 C/A for Rinex3. */
- C2C(MeasurementType.PSEUDO_RANGE, Frequency.G02, Frequency.R02),
+ C2C(MeasurementType.PSEUDO_RANGE, SignalCode.C, Frequency.G02, Frequency.R02),
/** Pseudorange GPS L1(C/A)+(P2-P1) (semi-codeless) for Rinex3. */
- C2D(MeasurementType.PSEUDO_RANGE, Frequency.G01),
+ C2D(MeasurementType.PSEUDO_RANGE, SignalCode.D, Frequency.G02),
/** Pseudorange Beidou B1 I for Rinex3.03. */
- C2I(MeasurementType.PSEUDO_RANGE, Frequency.B01),
+ C2I(MeasurementType.PSEUDO_RANGE, SignalCode.I, Frequency.B01),
/** Pseudorange GPS L2 L2C(L) / QZSS L2 L2C(2) for Rinex3. */
- C2L(MeasurementType.PSEUDO_RANGE, Frequency.G02, Frequency.J02),
+ C2L(MeasurementType.PSEUDO_RANGE, SignalCode.L, Frequency.G02, Frequency.J02),
/** Pseudorange GPS L2 M for Rinex3. */
- C2M(MeasurementType.PSEUDO_RANGE, Frequency.G02),
+ C2M(MeasurementType.PSEUDO_RANGE, SignalCode.M, Frequency.G02),
/** Pseudorange GPS L2 P(AS off) / GLONASS G2 P for Rinex3. */
- C2P(MeasurementType.PSEUDO_RANGE, Frequency.G02, Frequency.R02),
+ C2P(MeasurementType.PSEUDO_RANGE, SignalCode.P, Frequency.G02, Frequency.R02),
/** Pseudorange Beidou B1 Q for Rinex3.03. */
- C2Q(MeasurementType.PSEUDO_RANGE, Frequency.B01),
+ C2Q(MeasurementType.PSEUDO_RANGE, SignalCode.Q, Frequency.B01),
/** Pseudorange GPS L2 L2C(M) / QZSS L2 L2C(M) for Rinex3. */
- C2S(MeasurementType.PSEUDO_RANGE, Frequency.G02, Frequency.J02),
+ C2S(MeasurementType.PSEUDO_RANGE, SignalCode.S, Frequency.G02, Frequency.J02),
/** Pseudorange GPS L2 Z-tracking and similar (AS on) for Rinex3. */
- C2W(MeasurementType.PSEUDO_RANGE, Frequency.G02),
+ C2W(MeasurementType.PSEUDO_RANGE, SignalCode.W, Frequency.G02),
/** Pseudorange GPS L2 L2C (M+L) / QZSS L2 L2C(M+L) for Rinex3. */
- C2X(MeasurementType.PSEUDO_RANGE, Frequency.G02, Frequency.J02),
+ C2X(MeasurementType.PSEUDO_RANGE, SignalCode.X, Frequency.G02, Frequency.J02),
/** Pseudorange GPS L2 Y for Rinex3. */
- C2Y(MeasurementType.PSEUDO_RANGE, Frequency.G02),
+ C2Y(MeasurementType.PSEUDO_RANGE, SignalCode.Y, Frequency.G02),
/** Pseudorange GLONASS G3 I for Rinex3. */
- C3I(MeasurementType.PSEUDO_RANGE, Frequency.R03),
+ C3I(MeasurementType.PSEUDO_RANGE, SignalCode.I, Frequency.R03),
/** Pseudorange GLONASS G3 Q for Rinex3. */
- C3Q(MeasurementType.PSEUDO_RANGE, Frequency.R03),
+ C3Q(MeasurementType.PSEUDO_RANGE, SignalCode.Q, Frequency.R03),
/** Pseudorange GLONASS G3 I+Q for Rinex3. */
- C3X(MeasurementType.PSEUDO_RANGE, Frequency.R03),
+ C3X(MeasurementType.PSEUDO_RANGE, SignalCode.X, Frequency.R03),
+
+ /** Pseudorange GLONASS G1a L1OCd for Rinex3. */
+ C4A(MeasurementType.PSEUDO_RANGE, SignalCode.A, Frequency.R04),
+
+ /** Pseudorange GLONASS G1a L1OCp for Rinex3. */
+ C4B(MeasurementType.PSEUDO_RANGE, SignalCode.B, Frequency.R04),
+
+ /** Pseudorange GLONASS G1a L1OCd+L1OCd for Rinex3. */
+ C4X(MeasurementType.PSEUDO_RANGE, SignalCode.X, Frequency.R04),
/** Pseudorange IRNSS L5 A for Rinex3. */
- C5A(MeasurementType.PSEUDO_RANGE, Frequency.I05),
+ C5A(MeasurementType.PSEUDO_RANGE, SignalCode.A, Frequency.I05),
/** Pseudorange IRNSS L5 B for Rinex3. */
- C5B(MeasurementType.PSEUDO_RANGE, Frequency.I05),
+ C5B(MeasurementType.PSEUDO_RANGE, SignalCode.B, Frequency.I05),
/** Pseudorange IRNSS L5 C for Rinex3. */
- C5C(MeasurementType.PSEUDO_RANGE, Frequency.I05),
+ C5C(MeasurementType.PSEUDO_RANGE, SignalCode.C, Frequency.I05),
+
+ /** Pseudorange QZSS L5 D for Rinex3. */
+ C5D(MeasurementType.PSEUDO_RANGE, SignalCode.D, Frequency.J05),
/** Pseudorange GPS L5 I/ Galileo E5a F/NAV OS / SBAS L5 I / QZSS L5 I for Rinex3. */
- C5I(MeasurementType.PSEUDO_RANGE, Frequency.G05, Frequency.E05, Frequency.S05, Frequency.J05),
+ C5I(MeasurementType.PSEUDO_RANGE, SignalCode.I, Frequency.G05, Frequency.E05, Frequency.S05, Frequency.J05),
+
+ /** Pseudorange QZSS L5 P for Rinex3. */
+ C5P(MeasurementType.PSEUDO_RANGE, SignalCode.P, Frequency.J05),
/** Pseudorange GPS L5 Q/ Galileo E5a Q / SBAS L5 Q / QZSS L5 Q for Rinex3. */
- C5Q(MeasurementType.PSEUDO_RANGE, Frequency.G05, Frequency.E05, Frequency.S05, Frequency.J05),
+ C5Q(MeasurementType.PSEUDO_RANGE, SignalCode.Q, Frequency.G05, Frequency.E05, Frequency.S05, Frequency.J05),
/** Pseudorange GPS L5 I+Q/ Galileo E5a I+Q / SBAS L5 I+Q / QZSS L5 I+Q / IRNSS L5 B+C for Rinex3. */
- C5X(MeasurementType.PSEUDO_RANGE, Frequency.G05, Frequency.E05, Frequency.S05, Frequency.J05, Frequency.I05),
+ C5X(MeasurementType.PSEUDO_RANGE, SignalCode.X, Frequency.G05, Frequency.E05, Frequency.S05, Frequency.J05, Frequency.I05),
+
+ /** Pseudorange QZSS L5 D+P for Rinex3. */
+ C5Z(MeasurementType.PSEUDO_RANGE, SignalCode.Z, Frequency.J05),
- /** Pseudorange Galileo E6 A PRS for Rinex3. */
- C6A(MeasurementType.PSEUDO_RANGE, Frequency.E06),
+ /** Pseudorange Galileo E6 A PRS / GLONASS G2a L2CSI for Rinex3. */
+ C6A(MeasurementType.PSEUDO_RANGE, SignalCode.A, Frequency.E06, Frequency.R06),
- /** Pseudorange Galileo E6 B C/NAV CS for Rinex3. */
- C6B(MeasurementType.PSEUDO_RANGE, Frequency.E06),
+ /** Pseudorange Galileo E6 B C/NAV CS / GLONASS G2a L2OCp for Rinex3. */
+ C6B(MeasurementType.PSEUDO_RANGE, SignalCode.B, Frequency.E06, Frequency.R06),
/** Pseudorange Galileo E6 C no data for Rinex3. */
- C6C(MeasurementType.PSEUDO_RANGE, Frequency.E06),
+ C6C(MeasurementType.PSEUDO_RANGE, SignalCode.C, Frequency.E06),
+
+ /** Pseudorange QZSS L6E for Rinex3. */
+ C6E(MeasurementType.PSEUDO_RANGE, SignalCode.E, Frequency.J06),
/** Pseudorange Beidou B3 I for Rinex3. */
- C6I(MeasurementType.PSEUDO_RANGE, Frequency.B03),
+ C6I(MeasurementType.PSEUDO_RANGE, SignalCode.I, Frequency.B03),
/** Pseudorange Beidou B3 Q for Rinex3. */
- C6Q(MeasurementType.PSEUDO_RANGE, Frequency.B03),
+ C6Q(MeasurementType.PSEUDO_RANGE, SignalCode.Q, Frequency.B03),
/** Pseudorange QZSS LEX(6) L for Rinex3. */
- C6L(MeasurementType.PSEUDO_RANGE, Frequency.J06),
+ C6L(MeasurementType.PSEUDO_RANGE, SignalCode.L, Frequency.J06),
/** Pseudorange QZSS LEX(6) S for Rinex3. */
- C6S(MeasurementType.PSEUDO_RANGE, Frequency.J06),
+ C6S(MeasurementType.PSEUDO_RANGE, SignalCode.S, Frequency.J06),
- /** Pseudorange Galileo E6 B+C / QZSS LEX(6) S+L / Beidou B3 I+Q for Rinex3. */
- C6X(MeasurementType.PSEUDO_RANGE, Frequency.E06, Frequency.J06, Frequency.B03),
+ /** Pseudorange Galileo E6 B+C / QZSS LEX(6) S+L / Beidou B3 I+Q / GLONASS G2a L2CSI+L2OCp for Rinex3. */
+ C6X(MeasurementType.PSEUDO_RANGE, SignalCode.X, Frequency.E06, Frequency.J06, Frequency.B03, Frequency.R06),
- /** Pseudorange Galileo E6 A+B+C for Rinex3. */
- C6Z(MeasurementType.PSEUDO_RANGE, Frequency.E06),
+ /** Pseudorange Galileo E6 A+B+C / QZSS L6(D+E) for Rinex3. */
+ C6Z(MeasurementType.PSEUDO_RANGE, SignalCode.Z, Frequency.E06, Frequency.J06),
/** Pseudorange Galileo E5b I I/NAV OS/CS/SoL / Beidou B2 I for Rinex3. */
- C7I(MeasurementType.PSEUDO_RANGE, Frequency.E07, Frequency.B02),
+ C7I(MeasurementType.PSEUDO_RANGE, SignalCode.I, Frequency.E07, Frequency.B02),
/** Pseudorange Galileo Q no data / Beidou B2 Q for Rinex3. */
- C7Q(MeasurementType.PSEUDO_RANGE, Frequency.E07, Frequency.B02),
+ C7Q(MeasurementType.PSEUDO_RANGE, SignalCode.Q, Frequency.E07, Frequency.B02),
/** Pseudorange Galileo E5b I+Q / Beidou B2 I+Q for Rinex3. */
- C7X(MeasurementType.PSEUDO_RANGE, Frequency.E07, Frequency.B02),
+ C7X(MeasurementType.PSEUDO_RANGE, SignalCode.X, Frequency.E07, Frequency.B02),
/** Pseudorange Galileo E5(E5a+E5b) I for Rinex3. */
- C8I(MeasurementType.PSEUDO_RANGE, Frequency.E08),
+ C8I(MeasurementType.PSEUDO_RANGE, SignalCode.I, Frequency.E08),
/** Pseudorange Galileo E5(E5a+E5b) Q for Rinex3. */
- C8Q(MeasurementType.PSEUDO_RANGE, Frequency.E08),
+ C8Q(MeasurementType.PSEUDO_RANGE, SignalCode.Q, Frequency.E08),
/** Pseudorange Galileo E5(E5a+E5b) I+Q for Rinex3. */
- C8X(MeasurementType.PSEUDO_RANGE, Frequency.E08),
+ C8X(MeasurementType.PSEUDO_RANGE, SignalCode.X, Frequency.E08),
/** Pseudorange IRNSS S A for Rinex3. */
- C9A(MeasurementType.PSEUDO_RANGE, Frequency.I09),
+ C9A(MeasurementType.PSEUDO_RANGE, SignalCode.A, Frequency.I09),
/** Pseudorange IRNSS S B for Rinex3. */
- C9B(MeasurementType.PSEUDO_RANGE, Frequency.I09),
+ C9B(MeasurementType.PSEUDO_RANGE, SignalCode.B, Frequency.I09),
/** Pseudorange IRNSS S C for Rinex3. */
- C9C(MeasurementType.PSEUDO_RANGE, Frequency.I09),
+ C9C(MeasurementType.PSEUDO_RANGE, SignalCode.C, Frequency.I09),
/** Pseudorange IRNSS S B+C for Rinex3. */
- C9X(MeasurementType.PSEUDO_RANGE, Frequency.I09),
+ C9X(MeasurementType.PSEUDO_RANGE, SignalCode.X, Frequency.I09),
/** Pseudorange GPS L1 C/A / GLONASS G1 C/A for Rinex2. */
- CA(MeasurementType.PSEUDO_RANGE, Frequency.G01, Frequency.R01),
+ CA(MeasurementType.PSEUDO_RANGE, SignalCode.C, Frequency.G01, Frequency.R01),
/** Pseudorange GPS L1C for Rinex2. */
- CB(MeasurementType.PSEUDO_RANGE, Frequency.G01),
+ CB(MeasurementType.PSEUDO_RANGE, SignalCode.L, Frequency.G01),
/** Pseudorange GPS L2C for Rinex2. */
- CC(MeasurementType.PSEUDO_RANGE, Frequency.G02),
+ CC(MeasurementType.PSEUDO_RANGE, SignalCode.L, Frequency.G02),
/** Pseudorange GLONASS G2 for Rinex2. */
- CD(MeasurementType.PSEUDO_RANGE, Frequency.R02),
+ CD(MeasurementType.PSEUDO_RANGE, SignalCode.C, Frequency.R02),
/** Doppler Galileo E1 A for Rinex3. */
- D1A(MeasurementType.DOPPLER, Frequency.E01),
+ D1A(MeasurementType.DOPPLER, SignalCode.A, Frequency.E01),
/** Doppler Galileo E1 I/NAV OS/CS/SoL for Rinex3. */
- D1B(MeasurementType.DOPPLER, Frequency.E01),
+ D1B(MeasurementType.DOPPLER, SignalCode.B, Frequency.E01),
/** Doppler GPS L1 C/A / GLONASS G1 C/A / Galileo E1 C / SBAS L1 C/A / QZSS L1 C/A for Rinex3. */
- D1C(MeasurementType.DOPPLER, Frequency.G01, Frequency.R01, Frequency.E01, Frequency.S01, Frequency.J01),
+ D1C(MeasurementType.DOPPLER, SignalCode.C, Frequency.G01, Frequency.R01, Frequency.E01, Frequency.S01, Frequency.J01),
/** Doppler Beidou B1 I for Rinex3. */
- D1I(MeasurementType.DOPPLER, Frequency.B01),
+ D1I(MeasurementType.DOPPLER, SignalCode.I, Frequency.B01),
/** Doppler GPS L1 L1C(P) / QZSS L1 L1C(P) for Rinex3. */
- D1L(MeasurementType.DOPPLER, Frequency.G01, Frequency.J01),
+ D1L(MeasurementType.DOPPLER, SignalCode.L, Frequency.G01, Frequency.J01),
/** Doppler GPS L2 M for Rinex3. */
- D1M(MeasurementType.DOPPLER, Frequency.G02),
+ D1M(MeasurementType.DOPPLER, SignalCode.M, Frequency.G02),
/** Doppler GPS L1 codeless for Rinex3. */
- D1N(MeasurementType.DOPPLER, Frequency.G01),
+ D1N(MeasurementType.DOPPLER, SignalCode.CODELESS, Frequency.G01),
/** Doppler GPS L2 P(AS off) / GLONASS G2 P for Rinex3. */
- D1P(MeasurementType.DOPPLER, Frequency.G02, Frequency.R02),
+ D1P(MeasurementType.DOPPLER, SignalCode.P, Frequency.G02, Frequency.R02),
/** Doppler GPS L1 L1C(D) / QZSS L1 L1C(D) for Rinex3. */
- D1S(MeasurementType.DOPPLER, Frequency.G01, Frequency.J01),
+ D1S(MeasurementType.DOPPLER, SignalCode.S, Frequency.G01, Frequency.J01),
/** Doppler GPS L1 Z-tracking and similar (AS on) for Rinex3. */
- D1W(MeasurementType.DOPPLER, Frequency.G01),
+ D1W(MeasurementType.DOPPLER, SignalCode.W, Frequency.G01),
/** Doppler GPS L1 L1C (D+P) / Galileo E1 B+C / QZSS L1 L1C(D+P) for Rinex3. */
- D1X(MeasurementType.DOPPLER, Frequency.G01, Frequency.E01, Frequency.J01),
+ D1X(MeasurementType.DOPPLER, SignalCode.X, Frequency.G01, Frequency.E01, Frequency.J01),
/** Doppler GPS L1 Y for Rinex3. */
- D1Y(MeasurementType.DOPPLER, Frequency.G01),
+ D1Y(MeasurementType.DOPPLER, SignalCode.Y, Frequency.G01),
/** Doppler Galileo E1 C1Z A+B+C / QZSS L1 L1-SAIF for Rinex3. */
- D1Z(MeasurementType.DOPPLER, Frequency.E01, Frequency.J01),
+ D1Z(MeasurementType.DOPPLER, SignalCode.Z, Frequency.E01, Frequency.J01),
/** Doppler GPS L2 C/A / GLONASS G2 C/A for Rinex3. */
- D2C(MeasurementType.DOPPLER, Frequency.G02, Frequency.R02),
+ D2C(MeasurementType.DOPPLER, SignalCode.C, Frequency.G02, Frequency.R02),
/** Doppler GPS L1(C/A)+(P2-P1) (semi-codeless) for Rinex3. */
- D2D(MeasurementType.DOPPLER, Frequency.G01),
+ D2D(MeasurementType.DOPPLER, SignalCode.D, Frequency.G02),
/** Doppler Beidou B1 I for Rinex3.03. */
- D2I(MeasurementType.DOPPLER, Frequency.B01),
+ D2I(MeasurementType.DOPPLER, SignalCode.I, Frequency.B01),
/** Doppler GPS L2 L2C(L) / QZSS L2 L2C(2) for Rinex3. */
- D2L(MeasurementType.DOPPLER, Frequency.G02, Frequency.J02),
+ D2L(MeasurementType.DOPPLER, SignalCode.L, Frequency.G02, Frequency.J02),
/** Doppler GPS L2 M for Rinex3. */
- D2M(MeasurementType.DOPPLER, Frequency.G02),
+ D2M(MeasurementType.DOPPLER, SignalCode.M, Frequency.G02),
/** Doppler GPS L2 codeless for Rinex3. */
- D2N(MeasurementType.DOPPLER, Frequency.G02),
+ D2N(MeasurementType.DOPPLER, SignalCode.CODELESS, Frequency.G02),
/** Doppler GPS L2 P(AS off) / GLONASS G2 P for Rinex3. */
- D2P(MeasurementType.DOPPLER, Frequency.G02, Frequency.R02),
+ D2P(MeasurementType.DOPPLER, SignalCode.P, Frequency.G02, Frequency.R02),
/** Doppler Beidou B1 Q for Rinex3.03. */
- D2Q(MeasurementType.DOPPLER, Frequency.B01),
+ D2Q(MeasurementType.DOPPLER, SignalCode.Q, Frequency.B01),
/** Doppler GPS L2 L2C(M) / QZSS L2 L2C(M) for Rinex3. */
- D2S(MeasurementType.DOPPLER, Frequency.G02, Frequency.J02),
+ D2S(MeasurementType.DOPPLER, SignalCode.S, Frequency.G02, Frequency.J02),
/** Doppler GPS L2 Z-tracking and similar (AS on) for Rinex3. */
- D2W(MeasurementType.DOPPLER, Frequency.G02),
+ D2W(MeasurementType.DOPPLER, SignalCode.W, Frequency.G02),
/** Doppler GPS L2 L2C (M+L) / QZSS L2 L2C(M+L) for Rinex3. */
- D2X(MeasurementType.DOPPLER, Frequency.G02, Frequency.J02),
+ D2X(MeasurementType.DOPPLER, SignalCode.X, Frequency.G02, Frequency.J02),
/** Doppler GPS L2 Y for Rinex3. */
- D2Y(MeasurementType.DOPPLER, Frequency.G02),
+ D2Y(MeasurementType.DOPPLER, SignalCode.Y, Frequency.G02),
/** Doppler GLONASS G3 I for Rinex3. */
- D3I(MeasurementType.DOPPLER, Frequency.R03),
+ D3I(MeasurementType.DOPPLER, SignalCode.I, Frequency.R03),
/** Doppler GLONASS G3 Q for Rinex3. */
- D3Q(MeasurementType.DOPPLER, Frequency.R03),
+ D3Q(MeasurementType.DOPPLER, SignalCode.Q, Frequency.R03),
/** Doppler GLONASS G3 I+Q for Rinex3. */
- D3X(MeasurementType.DOPPLER, Frequency.R03),
+ D3X(MeasurementType.DOPPLER, SignalCode.X, Frequency.R03),
+
+ /** Doppler GLONASS G1a L1OCd for Rinex3. */
+ D4A(MeasurementType.DOPPLER, SignalCode.A, Frequency.R04),
+
+ /** Doppler GLONASS G1a L1OCp for Rinex3. */
+ D4B(MeasurementType.DOPPLER, SignalCode.B, Frequency.R04),
+
+ /** Doppler GLONASS G1a L1OCd+L1OCd for Rinex3. */
+ D4X(MeasurementType.DOPPLER, SignalCode.X, Frequency.R04),
/** Doppler IRNSS L5 A for Rinex3. */
- D5A(MeasurementType.DOPPLER, Frequency.I05),
+ D5A(MeasurementType.DOPPLER, SignalCode.A, Frequency.I05),
/** Doppler IRNSS L5 B for Rinex3. */
- D5B(MeasurementType.DOPPLER, Frequency.I05),
+ D5B(MeasurementType.DOPPLER, SignalCode.B, Frequency.I05),
/** Doppler IRNSS L5 C for Rinex3. */
- D5C(MeasurementType.DOPPLER, Frequency.I05),
+ D5C(MeasurementType.DOPPLER, SignalCode.C, Frequency.I05),
+
+ /** Doppler QZSS L5 D for Rinex3. */
+ D5D(MeasurementType.DOPPLER, SignalCode.D, Frequency.J05),
/** Doppler GPS L5 I/ Galileo E5a F/NAV OS / SBAS L5 I / QZSS L5 I for Rinex3. */
- D5I(MeasurementType.DOPPLER, Frequency.G05, Frequency.E05, Frequency.S05, Frequency.J05),
+ D5I(MeasurementType.DOPPLER, SignalCode.I, Frequency.G05, Frequency.E05, Frequency.S05, Frequency.J05),
+
+ /** Doppler QZSS L5 P for Rinex3. */
+ D5P(MeasurementType.DOPPLER, SignalCode.P, Frequency.J05),
/** Doppler GPS L5 Q/ Galileo E5a Q / SBAS L5 Q / QZSS L5 Q for Rinex3. */
- D5Q(MeasurementType.DOPPLER, Frequency.G05, Frequency.E05, Frequency.S05, Frequency.J05),
+ D5Q(MeasurementType.DOPPLER, SignalCode.Q, Frequency.G05, Frequency.E05, Frequency.S05, Frequency.J05),
/** Doppler GPS L5 I+Q/ Galileo E5a I+Q / SBAS L5 I+Q / QZSS L5 I+Q / IRNSS L5 B+C for Rinex3. */
- D5X(MeasurementType.DOPPLER, Frequency.G05, Frequency.E05, Frequency.S05, Frequency.J05, Frequency.I05),
+ D5X(MeasurementType.DOPPLER, SignalCode.X, Frequency.G05, Frequency.E05, Frequency.S05, Frequency.J05, Frequency.I05),
- /** Doppler Galileo E6 A PRS for Rinex3. */
- D6A(MeasurementType.DOPPLER, Frequency.E06),
+ /** Doppler QZSS L5 D+P for Rinex3. */
+ D5Z(MeasurementType.DOPPLER, SignalCode.Z, Frequency.J05),
- /** Doppler Galileo E6 B C/NAV CS for Rinex3. */
- D6B(MeasurementType.DOPPLER, Frequency.E06),
+ /** Doppler Galileo E6 A PRS / GLONASS L2CSI for Rinex3. */
+ D6A(MeasurementType.DOPPLER, SignalCode.A, Frequency.E06, Frequency.R06),
+
+ /** Doppler Galileo E6 B C/NAV CS / GLONASS L2OCp for Rinex3. */
+ D6B(MeasurementType.DOPPLER, SignalCode.B, Frequency.E06, Frequency.R06),
/** Doppler Galileo E6 C no data for Rinex3. */
- D6C(MeasurementType.DOPPLER, Frequency.E06),
+ D6C(MeasurementType.DOPPLER, SignalCode.C, Frequency.E06),
+
+ /** Doppler QZSS L6E for Rinex3. */
+ D6E(MeasurementType.DOPPLER, SignalCode.E, Frequency.J06),
/** Doppler Beidou B3 I for Rinex3. */
- D6I(MeasurementType.DOPPLER, Frequency.B03),
+ D6I(MeasurementType.DOPPLER, SignalCode.I, Frequency.B03),
/** Doppler Beidou B3 Q for Rinex3. */
- D6Q(MeasurementType.DOPPLER, Frequency.B03),
+ D6Q(MeasurementType.DOPPLER, SignalCode.Q, Frequency.B03),
/** Doppler QZSS LEX(6) L for Rinex3. */
- D6L(MeasurementType.DOPPLER, Frequency.J06),
+ D6L(MeasurementType.DOPPLER, SignalCode.L, Frequency.J06),
/** Doppler QZSS LEX(6) S for Rinex3. */
- D6S(MeasurementType.DOPPLER, Frequency.J06),
+ D6S(MeasurementType.DOPPLER, SignalCode.S, Frequency.J06),
- /** Doppler Galileo E6 B+C / QZSS LEX(6) S+L / Beidou B3 I+Q for Rinex3. */
- D6X(MeasurementType.DOPPLER, Frequency.E06, Frequency.J06, Frequency.B03),
+ /** Doppler Galileo E6 B+C / QZSS LEX(6) S+L / Beidou B3 I+Q / GLONASS G2a L2CSI+L2OCp for Rinex3. */
+ D6X(MeasurementType.DOPPLER, SignalCode.X, Frequency.E06, Frequency.J06, Frequency.B03, Frequency.R06),
- /** Doppler Galileo E6 A+B+C for Rinex3. */
- D6Z(MeasurementType.DOPPLER, Frequency.E06),
+ /** Doppler Galileo E6 A+B+C / QZSS L6(D+E) for Rinex3. */
+ D6Z(MeasurementType.DOPPLER, SignalCode.Z, Frequency.E06, Frequency.J06),
/** Doppler Galileo E5b I I/NAV OS/CS/SoL / Beidou B2 I for Rinex3. */
- D7I(MeasurementType.DOPPLER, Frequency.E07, Frequency.B02),
+ D7I(MeasurementType.DOPPLER, SignalCode.I, Frequency.E07, Frequency.B02),
/** Doppler Galileo Q no data / Beidou B2 Q for Rinex3. */
- D7Q(MeasurementType.DOPPLER, Frequency.E07, Frequency.B02),
+ D7Q(MeasurementType.DOPPLER, SignalCode.Q, Frequency.E07, Frequency.B02),
/** Doppler Galileo E5b I+Q / Beidou B2 I+Q for Rinex3. */
- D7X(MeasurementType.DOPPLER, Frequency.E07, Frequency.B02),
+ D7X(MeasurementType.DOPPLER, SignalCode.X, Frequency.E07, Frequency.B02),
/** Doppler Galileo E5(E5a+E5b) I for Rinex3. */
- D8I(MeasurementType.DOPPLER, Frequency.E08),
+ D8I(MeasurementType.DOPPLER, SignalCode.I, Frequency.E08),
/** Doppler Galileo E5(E5a+E5b) Q for Rinex3. */
- D8Q(MeasurementType.DOPPLER, Frequency.E08),
+ D8Q(MeasurementType.DOPPLER, SignalCode.Q, Frequency.E08),
/** Doppler Galileo E5(E5a+E5b) I+Q for Rinex3. */
- D8X(MeasurementType.DOPPLER, Frequency.E08),
+ D8X(MeasurementType.DOPPLER, SignalCode.X, Frequency.E08),
/** Doppler IRNSS S A for Rinex3. */
- D9A(MeasurementType.DOPPLER, Frequency.I09),
+ D9A(MeasurementType.DOPPLER, SignalCode.A, Frequency.I09),
/** Doppler IRNSS S B for Rinex3. */
- D9B(MeasurementType.DOPPLER, Frequency.I09),
+ D9B(MeasurementType.DOPPLER, SignalCode.B, Frequency.I09),
/** Doppler IRNSS S C for Rinex3. */
- D9C(MeasurementType.DOPPLER, Frequency.I09),
+ D9C(MeasurementType.DOPPLER, SignalCode.C, Frequency.I09),
/** Doppler IRNSS S B+C for Rinex3. */
- D9X(MeasurementType.DOPPLER, Frequency.I09),
+ D9X(MeasurementType.DOPPLER, SignalCode.X, Frequency.I09),
/** Doppler GPS L1 C/A / GLONASS G1 C/A for Rinex2. */
- DA(MeasurementType.DOPPLER, Frequency.G01, Frequency.R01),
+ DA(MeasurementType.DOPPLER, SignalCode.C, Frequency.G01, Frequency.R01),
/** Doppler GPS L1C for Rinex2. */
- DB(MeasurementType.DOPPLER, Frequency.G01),
+ DB(MeasurementType.DOPPLER, SignalCode.L, Frequency.G01),
/** Doppler GPS L2C for Rinex2. */
- DC(MeasurementType.DOPPLER, Frequency.G02),
+ DC(MeasurementType.DOPPLER, SignalCode.L, Frequency.G02),
/** Doppler GLONASS G2 for Rinex2. */
- DD(MeasurementType.DOPPLER, Frequency.R02),
+ DD(MeasurementType.DOPPLER, SignalCode.C, Frequency.R02),
/** Carrier-phase Galileo E1 A for Rinex3. */
- L1A(MeasurementType.CARRIER_PHASE, Frequency.E01),
+ L1A(MeasurementType.CARRIER_PHASE, SignalCode.A, Frequency.E01),
/** Carrier-phase Galileo E1 I/NAV OS/CS/SoL for Rinex3. */
- L1B(MeasurementType.CARRIER_PHASE, Frequency.E01),
+ L1B(MeasurementType.CARRIER_PHASE, SignalCode.B, Frequency.E01),
/** Carrier-phase GPS L1 C/A / GLONASS G1 C/A / Galileo E1 C / SBAS L1 C/A / QZSS L1 C/A for Rinex3. */
- L1C(MeasurementType.CARRIER_PHASE, Frequency.G01, Frequency.R01, Frequency.E01, Frequency.S01, Frequency.J01),
+ L1C(MeasurementType.CARRIER_PHASE, SignalCode.C, Frequency.G01, Frequency.R01, Frequency.E01, Frequency.S01, Frequency.J01),
/** Carrier-phase Beidou B1 I for Rinex3. */
- L1I(MeasurementType.CARRIER_PHASE, Frequency.B01),
+ L1I(MeasurementType.CARRIER_PHASE, SignalCode.I, Frequency.B01),
/** Carrier-phase GPS L1 L1C(P) / QZSS L1 L1C(P) for Rinex3. */
- L1L(MeasurementType.CARRIER_PHASE, Frequency.G01, Frequency.J01),
+ L1L(MeasurementType.CARRIER_PHASE, SignalCode.L, Frequency.G01, Frequency.J01),
/** Carrier-phase GPS L2 M for Rinex3. */
- L1M(MeasurementType.CARRIER_PHASE, Frequency.G02),
+ L1M(MeasurementType.CARRIER_PHASE, SignalCode.M, Frequency.G02),
/** Carrier-phase GPS L1 codeless for Rinex3. */
- L1N(MeasurementType.CARRIER_PHASE, Frequency.G01),
+ L1N(MeasurementType.CARRIER_PHASE, SignalCode.CODELESS, Frequency.G01),
/** Carrier-phase GPS L2 P(AS off) / GLONASS G2 P for Rinex3. */
- L1P(MeasurementType.CARRIER_PHASE, Frequency.G02, Frequency.R02),
+ L1P(MeasurementType.CARRIER_PHASE, SignalCode.P, Frequency.G02, Frequency.R02),
/** Carrier-phase GPS L1 L1C(D) / QZSS L1 L1C(D) for Rinex3. */
- L1S(MeasurementType.CARRIER_PHASE, Frequency.G01, Frequency.J01),
+ L1S(MeasurementType.CARRIER_PHASE, SignalCode.S, Frequency.G01, Frequency.J01),
/** Carrier-phase GPS L1 Z-tracking and similar (AS on) for Rinex3. */
- L1W(MeasurementType.CARRIER_PHASE, Frequency.G01),
+ L1W(MeasurementType.CARRIER_PHASE, SignalCode.W, Frequency.G01),
/** Carrier-phase GPS L1 L1C (D+P) / Galileo E1 B+C / QZSS L1 L1C(D+P) for Rinex3. */
- L1X(MeasurementType.CARRIER_PHASE, Frequency.G01, Frequency.E01, Frequency.J01),
+ L1X(MeasurementType.CARRIER_PHASE, SignalCode.X, Frequency.G01, Frequency.E01, Frequency.J01),
/** Carrier-phase GPS L1 Y for Rinex3. */
- L1Y(MeasurementType.CARRIER_PHASE, Frequency.G01),
+ L1Y(MeasurementType.CARRIER_PHASE, SignalCode.Y, Frequency.G01),
/** Carrier-phase Galileo E1 C1Z A+B+C / QZSS L1 L1-SAIF for Rinex3. */
- L1Z(MeasurementType.CARRIER_PHASE, Frequency.E01, Frequency.J01),
+ L1Z(MeasurementType.CARRIER_PHASE, SignalCode.Z, Frequency.E01, Frequency.J01),
/** Carrier-phase GPS L2 C/A / GLONASS G2 C/A for Rinex3. */
- L2C(MeasurementType.CARRIER_PHASE, Frequency.G02, Frequency.R02),
+ L2C(MeasurementType.CARRIER_PHASE, SignalCode.C, Frequency.G02, Frequency.R02),
/** Carrier-phase GPS L1(C/A)+(P2-P1) (semi-codeless) for Rinex3. */
- L2D(MeasurementType.CARRIER_PHASE, Frequency.G01),
+ L2D(MeasurementType.CARRIER_PHASE, SignalCode.D, Frequency.G02),
/** Carrier-phase Beidou B1 I for Rinex3.03. */
- L2I(MeasurementType.CARRIER_PHASE, Frequency.B01),
+ L2I(MeasurementType.CARRIER_PHASE, SignalCode.I, Frequency.B01),
/** Carrier-phase GPS L2 L2C(L) / QZSS L2 L2C(2) for Rinex3. */
- L2L(MeasurementType.CARRIER_PHASE, Frequency.G02, Frequency.J02),
+ L2L(MeasurementType.CARRIER_PHASE, SignalCode.L, Frequency.G02, Frequency.J02),
/** Carrier-phase GPS L2 M for Rinex3. */
- L2M(MeasurementType.CARRIER_PHASE, Frequency.G02),
+ L2M(MeasurementType.CARRIER_PHASE, SignalCode.M, Frequency.G02),
/** Carrier-phase GPS L2 codeless for Rinex3. */
- L2N(MeasurementType.CARRIER_PHASE, Frequency.G02),
+ L2N(MeasurementType.CARRIER_PHASE, SignalCode.CODELESS, Frequency.G02),
/** Carrier-phase GPS L2 P(AS off) / GLONASS G2 P for Rinex3. */
- L2P(MeasurementType.CARRIER_PHASE, Frequency.G02, Frequency.R02),
+ L2P(MeasurementType.CARRIER_PHASE, SignalCode.P, Frequency.G02, Frequency.R02),
/** Carrier-phase Beidou B1 Q for Rinex3.03. */
- L2Q(MeasurementType.CARRIER_PHASE, Frequency.B01),
+ L2Q(MeasurementType.CARRIER_PHASE, SignalCode.Q, Frequency.B01),
/** Carrier-phase GPS L2 L2C(M) / QZSS L2 L2C(M) for Rinex3. */
- L2S(MeasurementType.CARRIER_PHASE, Frequency.G02, Frequency.J02),
+ L2S(MeasurementType.CARRIER_PHASE, SignalCode.S, Frequency.G02, Frequency.J02),
/** Carrier-phase GPS L2 Z-tracking and similar (AS on) for Rinex3. */
- L2W(MeasurementType.CARRIER_PHASE, Frequency.G02),
+ L2W(MeasurementType.CARRIER_PHASE, SignalCode.W, Frequency.G02),
/** Carrier-phase GPS L2 L2C (M+L) / QZSS L2 L2C(M+L) for Rinex3. */
- L2X(MeasurementType.CARRIER_PHASE, Frequency.G02, Frequency.J02),
+ L2X(MeasurementType.CARRIER_PHASE, SignalCode.X, Frequency.G02, Frequency.J02),
/** Carrier-phase GPS L2 Y for Rinex3. */
- L2Y(MeasurementType.CARRIER_PHASE, Frequency.G02),
+ L2Y(MeasurementType.CARRIER_PHASE, SignalCode.Y, Frequency.G02),
/** Carrier-phase GLONASS G3 I for Rinex3. */
- L3I(MeasurementType.CARRIER_PHASE, Frequency.R03),
+ L3I(MeasurementType.CARRIER_PHASE, SignalCode.I, Frequency.R03),
/** Carrier-phase GLONASS G3 Q for Rinex3. */
- L3Q(MeasurementType.CARRIER_PHASE, Frequency.R03),
+ L3Q(MeasurementType.CARRIER_PHASE, SignalCode.Q, Frequency.R03),
/** Carrier-phase GLONASS G3 I+Q for Rinex3. */
- L3X(MeasurementType.CARRIER_PHASE, Frequency.R03),
+ L3X(MeasurementType.CARRIER_PHASE, SignalCode.X, Frequency.R03),
+
+ /** Carrier-phase GLONASS G1a L1OCd for Rinex3. */
+ L4A(MeasurementType.CARRIER_PHASE, SignalCode.A, Frequency.R04),
+
+ /** Carrier-phase GLONASS G1a L1OCp for Rinex3. */
+ L4B(MeasurementType.CARRIER_PHASE, SignalCode.B, Frequency.R04),
+
+ /** Carrier-phase GLONASS G1a L1OCd+L1OCd for Rinex3. */
+ L4X(MeasurementType.CARRIER_PHASE, SignalCode.X, Frequency.R04),
/** Carrier-phase IRNSS L5 A for Rinex3. */
- L5A(MeasurementType.CARRIER_PHASE, Frequency.I05),
+ L5A(MeasurementType.CARRIER_PHASE, SignalCode.A, Frequency.I05),
/** Carrier-phase IRNSS L5 B for Rinex3. */
- L5B(MeasurementType.CARRIER_PHASE, Frequency.I05),
+ L5B(MeasurementType.CARRIER_PHASE, SignalCode.B, Frequency.I05),
/** Carrier-phase IRNSS L5 C for Rinex3. */
- L5C(MeasurementType.CARRIER_PHASE, Frequency.I05),
+ L5C(MeasurementType.CARRIER_PHASE, SignalCode.C, Frequency.I05),
+
+ /** Carrier-phase QZSS L5 D for Rinex3. */
+ L5D(MeasurementType.CARRIER_PHASE, SignalCode.D, Frequency.J05),
/** Carrier-phase GPS L5 I/ Galileo E5a F/NAV OS / SBAS L5 I / QZSS L5 I for Rinex3. */
- L5I(MeasurementType.CARRIER_PHASE, Frequency.G05, Frequency.E05, Frequency.S05, Frequency.J05),
+ L5I(MeasurementType.CARRIER_PHASE, SignalCode.I, Frequency.G05, Frequency.E05, Frequency.S05, Frequency.J05),
+
+ /** Carrier-phase QZSS L5 P for Rinex3. */
+ L5P(MeasurementType.CARRIER_PHASE, SignalCode.P, Frequency.J05),
/** Carrier-phase GPS L5 Q/ Galileo E5a Q / SBAS L5 Q / QZSS L5 Q for Rinex3. */
- L5Q(MeasurementType.CARRIER_PHASE, Frequency.G05, Frequency.E05, Frequency.S05, Frequency.J05),
+ L5Q(MeasurementType.CARRIER_PHASE, SignalCode.Q, Frequency.G05, Frequency.E05, Frequency.S05, Frequency.J05),
/** Carrier-phase GPS L5 I+Q/ Galileo E5a I+Q / SBAS L5 I+Q / QZSS L5 I+Q / IRNSS L5 B+C for Rinex3. */
- L5X(MeasurementType.CARRIER_PHASE, Frequency.G05, Frequency.E05, Frequency.S05, Frequency.J05, Frequency.I05),
+ L5X(MeasurementType.CARRIER_PHASE, SignalCode.X, Frequency.G05, Frequency.E05, Frequency.S05, Frequency.J05, Frequency.I05),
+
+ /** Carrier-phase QZSS L5 D+P for Rinex3. */
+ L5Z(MeasurementType.CARRIER_PHASE, SignalCode.Z, Frequency.J05),
- /** Carrier-phase Galileo E6 A PRS for Rinex3. */
- L6A(MeasurementType.CARRIER_PHASE, Frequency.E06),
+ /** Carrier-phase Galileo E6 A PRS / GLONASS G2a L2CSI for Rinex3. */
+ L6A(MeasurementType.CARRIER_PHASE, SignalCode.A, Frequency.E06, Frequency.R06),
- /** Carrier-phase Galileo E6 B C/NAV CS for Rinex3. */
- L6B(MeasurementType.CARRIER_PHASE, Frequency.E06),
+ /** Carrier-phase Galileo E6 B C/NAV CS / GLONASS G2a L2OCp for Rinex3. */
+ L6B(MeasurementType.CARRIER_PHASE, SignalCode.B, Frequency.E06, Frequency.R06),
/** Carrier-phase Galileo E6 C no data for Rinex3. */
- L6C(MeasurementType.CARRIER_PHASE, Frequency.E06),
+ L6C(MeasurementType.CARRIER_PHASE, SignalCode.C, Frequency.E06),
+
+ /** Carrier-phase QZSS L6E for Rinex3. */
+ L6E(MeasurementType.CARRIER_PHASE, SignalCode.E, Frequency.J06),
/** Carrier-phase Beidou B3 I for Rinex3. */
- L6I(MeasurementType.CARRIER_PHASE, Frequency.B03),
+ L6I(MeasurementType.CARRIER_PHASE, SignalCode.I, Frequency.B03),
/** Carrier-phase Beidou B3 Q for Rinex3. */
- L6Q(MeasurementType.CARRIER_PHASE, Frequency.B03),
+ L6Q(MeasurementType.CARRIER_PHASE, SignalCode.Q, Frequency.B03),
/** Carrier-phase QZSS LEX(6) L for Rinex3. */
- L6L(MeasurementType.CARRIER_PHASE, Frequency.J06),
+ L6L(MeasurementType.CARRIER_PHASE, SignalCode.L, Frequency.J06),
/** Carrier-phase QZSS LEX(6) S for Rinex3. */
- L6S(MeasurementType.CARRIER_PHASE, Frequency.J06),
+ L6S(MeasurementType.CARRIER_PHASE, SignalCode.S, Frequency.J06),
- /** Carrier-phase Galileo E6 B+C / QZSS LEX(6) S+L / Beidou B3 I+Q for Rinex3. */
- L6X(MeasurementType.CARRIER_PHASE, Frequency.E06, Frequency.J06, Frequency.B03),
+ /** Carrier-phase Galileo E6 B+C / QZSS LEX(6) S+L / Beidou B3 I+Q / GLONASS G2a L2CSI+L2OCp for Rinex3. */
+ L6X(MeasurementType.CARRIER_PHASE, SignalCode.X, Frequency.E06, Frequency.J06, Frequency.B03, Frequency.R06),
- /** Carrier-phase Galileo E6 A+B+C for Rinex3. */
- L6Z(MeasurementType.CARRIER_PHASE, Frequency.E06),
+ /** Carrier-phase Galileo E6 A+B+C / QZSS L6(D+E) for Rinex3. */
+ L6Z(MeasurementType.CARRIER_PHASE, SignalCode.Z, Frequency.E06, Frequency.J06),
/** Carrier-phase Galileo E5b I I/NAV OS/CS/SoL / Beidou B2 I for Rinex3. */
- L7I(MeasurementType.CARRIER_PHASE, Frequency.E07, Frequency.B02),
+ L7I(MeasurementType.CARRIER_PHASE, SignalCode.I, Frequency.E07, Frequency.B02),
/** Carrier-phase Galileo Q no data / Beidou B2 Q for Rinex3. */
- L7Q(MeasurementType.CARRIER_PHASE, Frequency.E07, Frequency.B02),
+ L7Q(MeasurementType.CARRIER_PHASE, SignalCode.Q, Frequency.E07, Frequency.B02),
/** Carrier-phase Galileo E5b I+Q / Beidou B2 I+Q for Rinex3. */
- L7X(MeasurementType.CARRIER_PHASE, Frequency.E07, Frequency.B02),
+ L7X(MeasurementType.CARRIER_PHASE, SignalCode.X, Frequency.E07, Frequency.B02),
/** Carrier-phase Galileo E5(E5a+E5b) I for Rinex3. */
- L8I(MeasurementType.CARRIER_PHASE, Frequency.E08),
+ L8I(MeasurementType.CARRIER_PHASE, SignalCode.I, Frequency.E08),
/** Carrier-phase Galileo E5(E5a+E5b) Q for Rinex3. */
- L8Q(MeasurementType.CARRIER_PHASE, Frequency.E08),
+ L8Q(MeasurementType.CARRIER_PHASE, SignalCode.Q, Frequency.E08),
/** Carrier-phase Galileo E5(E5a+E5b) I+Q for Rinex3. */
- L8X(MeasurementType.CARRIER_PHASE, Frequency.E08),
+ L8X(MeasurementType.CARRIER_PHASE, SignalCode.X, Frequency.E08),
/** Carrier-phase IRNSS S A for Rinex3. */
- L9A(MeasurementType.CARRIER_PHASE, Frequency.I09),
+ L9A(MeasurementType.CARRIER_PHASE, SignalCode.A, Frequency.I09),
/** Carrier-phase IRNSS S B for Rinex3. */
- L9B(MeasurementType.CARRIER_PHASE, Frequency.I09),
+ L9B(MeasurementType.CARRIER_PHASE, SignalCode.B, Frequency.I09),
/** Carrier-phase IRNSS S C for Rinex3. */
- L9C(MeasurementType.CARRIER_PHASE, Frequency.I09),
+ L9C(MeasurementType.CARRIER_PHASE, SignalCode.C, Frequency.I09),
/** Carrier-phase IRNSS S B+C for Rinex3. */
- L9X(MeasurementType.CARRIER_PHASE, Frequency.I09),
+ L9X(MeasurementType.CARRIER_PHASE, SignalCode.X, Frequency.I09),
/** Signal-strength Galileo E1 A for Rinex3. */
- S1A(MeasurementType.SIGNAL_STRENGTH, Frequency.E01),
+ S1A(MeasurementType.SIGNAL_STRENGTH, SignalCode.A, Frequency.E01),
/** Signal-strength Galileo E1 I/NAV OS/CS/SoL for Rinex3. */
- S1B(MeasurementType.SIGNAL_STRENGTH, Frequency.E01),
+ S1B(MeasurementType.SIGNAL_STRENGTH, SignalCode.B, Frequency.E01),
/** Signal-strength GPS L1 C/A / GLONASS G1 C/A / Galileo E1 C / SBAS L1 C/A / QZSS L1 C/A for Rinex3. */
- S1C(MeasurementType.SIGNAL_STRENGTH, Frequency.G01, Frequency.R01, Frequency.E01, Frequency.S01, Frequency.J01),
+ S1C(MeasurementType.SIGNAL_STRENGTH, SignalCode.C, Frequency.G01, Frequency.R01, Frequency.E01, Frequency.S01, Frequency.J01),
/** Signal-strength Beidou B1 I for Rinex3. */
- S1I(MeasurementType.SIGNAL_STRENGTH, Frequency.B01),
+ S1I(MeasurementType.SIGNAL_STRENGTH, SignalCode.I, Frequency.B01),
/** Signal-strength GPS L1 L1C(P) / QZSS L1 L1C(P) for Rinex3. */
- S1L(MeasurementType.SIGNAL_STRENGTH, Frequency.G01, Frequency.J01),
+ S1L(MeasurementType.SIGNAL_STRENGTH, SignalCode.L, Frequency.G01, Frequency.J01),
/** Signal-strength GPS L2 M for Rinex3. */
- S1M(MeasurementType.SIGNAL_STRENGTH, Frequency.G02),
+ S1M(MeasurementType.SIGNAL_STRENGTH, SignalCode.M, Frequency.G02),
/** Signal-strength GPS L1 codeless for Rinex3. */
- S1N(MeasurementType.SIGNAL_STRENGTH, Frequency.G01),
+ S1N(MeasurementType.SIGNAL_STRENGTH, SignalCode.CODELESS, Frequency.G01),
/** Signal-strength GPS L2 P(AS off) / GLONASS G2 P for Rinex3. */
- S1P(MeasurementType.SIGNAL_STRENGTH, Frequency.G02, Frequency.R02),
+ S1P(MeasurementType.SIGNAL_STRENGTH, SignalCode.P, Frequency.G02, Frequency.R02),
/** Signal-strength GPS L1 L1C(D) / QZSS L1 L1C(D) for Rinex3. */
- S1S(MeasurementType.SIGNAL_STRENGTH, Frequency.G01, Frequency.J01),
+ S1S(MeasurementType.SIGNAL_STRENGTH, SignalCode.S, Frequency.G01, Frequency.J01),
/** Signal-strength GPS L1 Z-tracking and similar (AS on) for Rinex3. */
- S1W(MeasurementType.SIGNAL_STRENGTH, Frequency.G01),
+ S1W(MeasurementType.SIGNAL_STRENGTH, SignalCode.W, Frequency.G01),
/** Signal-strength GPS L1 L1C (D+P) / Galileo E1 B+C / QZSS L1 L1C(D+P) for Rinex3. */
- S1X(MeasurementType.SIGNAL_STRENGTH, Frequency.G01, Frequency.E01, Frequency.J01),
+ S1X(MeasurementType.SIGNAL_STRENGTH, SignalCode.X, Frequency.G01, Frequency.E01, Frequency.J01),
/** Signal-strength GPS L1 Y for Rinex3. */
- S1Y(MeasurementType.SIGNAL_STRENGTH, Frequency.G01),
+ S1Y(MeasurementType.SIGNAL_STRENGTH, SignalCode.Y, Frequency.G01),
/** Signal-strength Galileo E1 C1Z A+B+C / QZSS L1 L1-SAIF for Rinex3. */
- S1Z(MeasurementType.SIGNAL_STRENGTH, Frequency.E01, Frequency.J01),
+ S1Z(MeasurementType.SIGNAL_STRENGTH, SignalCode.Z, Frequency.E01, Frequency.J01),
/** Signal-strength GPS L2 C/A / GLONASS G2 C/A for Rinex3. */
- S2C(MeasurementType.SIGNAL_STRENGTH, Frequency.G02, Frequency.R02),
+ S2C(MeasurementType.SIGNAL_STRENGTH, SignalCode.C, Frequency.G02, Frequency.R02),
/** Signal-strength GPS L1(C/A)+(P2-P1) (semi-codeless) for Rinex3. */
- S2D(MeasurementType.SIGNAL_STRENGTH, Frequency.G01),
+ S2D(MeasurementType.SIGNAL_STRENGTH, SignalCode.D, Frequency.G02),
/** Signal-strength Beidou B1 I for Rinex3.03. */
- S2I(MeasurementType.SIGNAL_STRENGTH, Frequency.B01),
+ S2I(MeasurementType.SIGNAL_STRENGTH, SignalCode.I, Frequency.B01),
/** Signal-strength GPS L2 L2C(L) / QZSS L2 L2C(2) for Rinex3. */
- S2L(MeasurementType.SIGNAL_STRENGTH, Frequency.G02, Frequency.J02),
+ S2L(MeasurementType.SIGNAL_STRENGTH, SignalCode.L, Frequency.G02, Frequency.J02),
/** Signal-strength GPS L2 M for Rinex3. */
- S2M(MeasurementType.SIGNAL_STRENGTH, Frequency.G02),
+ S2M(MeasurementType.SIGNAL_STRENGTH, SignalCode.M, Frequency.G02),
/** Signal-strength GPS L2 codeless for Rinex3. */
- S2N(MeasurementType.SIGNAL_STRENGTH, Frequency.G02),
+ S2N(MeasurementType.SIGNAL_STRENGTH, SignalCode.CODELESS, Frequency.G02),
/** Signal-strength GPS L2 P(AS off) / GLONASS G2 P for Rinex3. */
- S2P(MeasurementType.SIGNAL_STRENGTH, Frequency.G02, Frequency.R02),
+ S2P(MeasurementType.SIGNAL_STRENGTH, SignalCode.P, Frequency.G02, Frequency.R02),
/** Signal-strength Beidou B1 Q for Rinex3.03. */
- S2Q(MeasurementType.SIGNAL_STRENGTH, Frequency.B01),
+ S2Q(MeasurementType.SIGNAL_STRENGTH, SignalCode.Q, Frequency.B01),
/** Signal-strength GPS L2 L2C(M) / QZSS L2 L2C(M) for Rinex3. */
- S2S(MeasurementType.SIGNAL_STRENGTH, Frequency.G02, Frequency.J02),
+ S2S(MeasurementType.SIGNAL_STRENGTH, SignalCode.S, Frequency.G02, Frequency.J02),
/** Signal-strength GPS L2 Z-tracking and similar (AS on) for Rinex3. */
- S2W(MeasurementType.SIGNAL_STRENGTH, Frequency.G02),
+ S2W(MeasurementType.SIGNAL_STRENGTH, SignalCode.W, Frequency.G02),
/** Signal-strength GPS L2 L2C (M+L) / QZSS L2 L2C(M+L) for Rinex3. */
- S2X(MeasurementType.SIGNAL_STRENGTH, Frequency.G02, Frequency.J02),
+ S2X(MeasurementType.SIGNAL_STRENGTH, SignalCode.X, Frequency.G02, Frequency.J02),
/** Signal-strength GPS L2 Y for Rinex3. */
- S2Y(MeasurementType.SIGNAL_STRENGTH, Frequency.G02),
+ S2Y(MeasurementType.SIGNAL_STRENGTH, SignalCode.Y, Frequency.G02),
/** Signal-strength GLONASS G3 I for Rinex3. */
- S3I(MeasurementType.SIGNAL_STRENGTH, Frequency.R03),
+ S3I(MeasurementType.SIGNAL_STRENGTH, SignalCode.I, Frequency.R03),
/** Signal-strength GLONASS G3 Q for Rinex3. */
- S3Q(MeasurementType.SIGNAL_STRENGTH, Frequency.R03),
+ S3Q(MeasurementType.SIGNAL_STRENGTH, SignalCode.Q, Frequency.R03),
/** Signal-strength GLONASS G3 I+Q for Rinex3. */
- S3X(MeasurementType.SIGNAL_STRENGTH, Frequency.R03),
+ S3X(MeasurementType.SIGNAL_STRENGTH, SignalCode.X, Frequency.R03),
+
+ /** Signal-strength GLONASS G1a L1OCd for Rinex3. */
+ S4A(MeasurementType.SIGNAL_STRENGTH, SignalCode.A, Frequency.R04),
+
+ /** Signal-strength GLONASS G1a L1OCp for Rinex3. */
+ S4B(MeasurementType.SIGNAL_STRENGTH, SignalCode.B, Frequency.R04),
+
+ /** Signal-strength GLONASS G1a L1OCd+L1OCd for Rinex3. */
+ S4X(MeasurementType.SIGNAL_STRENGTH, SignalCode.X, Frequency.R04),
/** Signal-strength IRNSS L5 A for Rinex3. */
- S5A(MeasurementType.SIGNAL_STRENGTH, Frequency.I05),
+ S5A(MeasurementType.SIGNAL_STRENGTH, SignalCode.A, Frequency.I05),
/** Signal-strength IRNSS L5 B for Rinex3. */
- S5B(MeasurementType.SIGNAL_STRENGTH, Frequency.I05),
+ S5B(MeasurementType.SIGNAL_STRENGTH, SignalCode.B, Frequency.I05),
/** Signal-strength IRNSS L5 C for Rinex3. */
- S5C(MeasurementType.SIGNAL_STRENGTH, Frequency.I05),
+ S5C(MeasurementType.SIGNAL_STRENGTH, SignalCode.C, Frequency.I05),
+
+ /** Signal-strength QZSS L5 D for Rinex3. */
+ S5D(MeasurementType.SIGNAL_STRENGTH, SignalCode.D, Frequency.J05),
/** Signal-strength GPS L5 I/ Galileo E5a F/NAV OS / SBAS L5 I / QZSS L5 I for Rinex3. */
- S5I(MeasurementType.SIGNAL_STRENGTH, Frequency.G05, Frequency.E05, Frequency.S05, Frequency.J05),
+ S5I(MeasurementType.SIGNAL_STRENGTH, SignalCode.I, Frequency.G05, Frequency.E05, Frequency.S05, Frequency.J05),
+
+ /** Signal-strength QZSS L5 P for Rinex3. */
+ S5P(MeasurementType.SIGNAL_STRENGTH, SignalCode.P, Frequency.J05),
/** Signal-strength GPS L5 Q/ Galileo E5a Q / SBAS L5 Q / QZSS L5 Q for Rinex3. */
- S5Q(MeasurementType.SIGNAL_STRENGTH, Frequency.G05, Frequency.E05, Frequency.S05, Frequency.J05),
+ S5Q(MeasurementType.SIGNAL_STRENGTH, SignalCode.Q, Frequency.G05, Frequency.E05, Frequency.S05, Frequency.J05),
/** Signal-strength GPS L5 I+Q/ Galileo E5a I+Q / SBAS L5 I+Q / QZSS L5 I+Q / IRNSS L5 B+C for Rinex3. */
- S5X(MeasurementType.SIGNAL_STRENGTH, Frequency.G05, Frequency.E05, Frequency.S05, Frequency.J05, Frequency.I05),
+ S5X(MeasurementType.SIGNAL_STRENGTH, SignalCode.X, Frequency.G05, Frequency.E05, Frequency.S05, Frequency.J05, Frequency.I05),
- /** Signal-strength Galileo E6 A PRS for Rinex3. */
- S6A(MeasurementType.SIGNAL_STRENGTH, Frequency.E06),
+ /** Signal-strength QZSS L5 D+P for Rinex3. */
+ S5Z(MeasurementType.SIGNAL_STRENGTH, SignalCode.Z, Frequency.J05),
- /** Signal-strength Galileo E6 B C/NAV CS for Rinex3. */
- S6B(MeasurementType.SIGNAL_STRENGTH, Frequency.E06),
+ /** Signal-strength Galileo E6 A PRS / GLONASS G2a L2CSI for Rinex3. */
+ S6A(MeasurementType.SIGNAL_STRENGTH, SignalCode.A, Frequency.E06, Frequency.R06),
+
+ /** Signal-strength Galileo E6 B C/NAV CS / GLONASS G2a L2OCp for Rinex3. */
+ S6B(MeasurementType.SIGNAL_STRENGTH, SignalCode.B, Frequency.E06, Frequency.R06),
/** Signal-strength Galileo E6 C no data for Rinex3. */
- S6C(MeasurementType.SIGNAL_STRENGTH, Frequency.E06),
+ S6C(MeasurementType.SIGNAL_STRENGTH, SignalCode.C, Frequency.E06),
+
+ /** Signal-strength QZSS L6E for Rinex3. */
+ S6E(MeasurementType.SIGNAL_STRENGTH, SignalCode.E, Frequency.J06),
/** Signal-strength Beidou B3 I for Rinex3. */
- S6I(MeasurementType.SIGNAL_STRENGTH, Frequency.B03),
+ S6I(MeasurementType.SIGNAL_STRENGTH, SignalCode.I, Frequency.B03),
/** Signal-strength Beidou B3 Q for Rinex3. */
- S6Q(MeasurementType.SIGNAL_STRENGTH, Frequency.B03),
+ S6Q(MeasurementType.SIGNAL_STRENGTH, SignalCode.Q, Frequency.B03),
/** Signal-strength QZSS LEX(6) L for Rinex3. */
- S6L(MeasurementType.SIGNAL_STRENGTH, Frequency.J06),
+ S6L(MeasurementType.SIGNAL_STRENGTH, SignalCode.L, Frequency.J06),
/** Signal-strength QZSS LEX(6) S for Rinex3. */
- S6S(MeasurementType.SIGNAL_STRENGTH, Frequency.J06),
+ S6S(MeasurementType.SIGNAL_STRENGTH, SignalCode.S, Frequency.J06),
- /** Signal-strength Galileo E6 B+C / QZSS LEX(6) S+L / Beidou B3 I+Q for Rinex3. */
- S6X(MeasurementType.SIGNAL_STRENGTH, Frequency.E06, Frequency.J06, Frequency.B03),
+ /** Signal-strength Galileo E6 B+C / QZSS LEX(6) S+L / Beidou B3 I+Q / GLONASS G2a L2CSI+L2OCp for Rinex3. */
+ S6X(MeasurementType.SIGNAL_STRENGTH, SignalCode.X, Frequency.E06, Frequency.J06, Frequency.B03, Frequency.R06),
- /** Signal-strength Galileo E6 A+B+C for Rinex3. */
- S6Z(MeasurementType.SIGNAL_STRENGTH, Frequency.E06),
+ /** Signal-strength Galileo E6 A+B+C / QZSS L6(D+E) for Rinex3. */
+ S6Z(MeasurementType.SIGNAL_STRENGTH, SignalCode.Z, Frequency.E06, Frequency.J06),
/** Signal-strength Galileo E5b I I/NAV OS/CS/SoL / Beidou B2 I for Rinex3. */
- S7I(MeasurementType.SIGNAL_STRENGTH, Frequency.E07, Frequency.B02),
+ S7I(MeasurementType.SIGNAL_STRENGTH, SignalCode.I, Frequency.E07, Frequency.B02),
/** Signal-strength Galileo Q no data / Beidou B2 Q for Rinex3. */
- S7Q(MeasurementType.SIGNAL_STRENGTH, Frequency.E07, Frequency.B02),
+ S7Q(MeasurementType.SIGNAL_STRENGTH, SignalCode.Q, Frequency.E07, Frequency.B02),
/** Signal-strength Galileo E5b I+Q / Beidou B2 I+Q for Rinex3. */
- S7X(MeasurementType.SIGNAL_STRENGTH, Frequency.E07, Frequency.B02),
+ S7X(MeasurementType.SIGNAL_STRENGTH, SignalCode.X, Frequency.E07, Frequency.B02),
/** Signal-strength Galileo E5(E5a+E5b) I for Rinex3. */
- S8I(MeasurementType.SIGNAL_STRENGTH, Frequency.E08),
+ S8I(MeasurementType.SIGNAL_STRENGTH, SignalCode.I, Frequency.E08),
/** Signal-strength Galileo E5(E5a+E5b) Q for Rinex3. */
- S8Q(MeasurementType.SIGNAL_STRENGTH, Frequency.E08),
+ S8Q(MeasurementType.SIGNAL_STRENGTH, SignalCode.Q, Frequency.E08),
/** Signal-strength Galileo E5(E5a+E5b) I+Q for Rinex3. */
- S8X(MeasurementType.SIGNAL_STRENGTH, Frequency.E08),
+ S8X(MeasurementType.SIGNAL_STRENGTH, SignalCode.X, Frequency.E08),
/** Signal-strength IRNSS S A for Rinex3. */
- S9A(MeasurementType.SIGNAL_STRENGTH, Frequency.I09),
+ S9A(MeasurementType.SIGNAL_STRENGTH, SignalCode.A, Frequency.I09),
/** Signal-strength IRNSS S B for Rinex3. */
- S9B(MeasurementType.SIGNAL_STRENGTH, Frequency.I09),
+ S9B(MeasurementType.SIGNAL_STRENGTH, SignalCode.B, Frequency.I09),
/** Signal-strength IRNSS S C for Rinex3. */
- S9C(MeasurementType.SIGNAL_STRENGTH, Frequency.I09),
+ S9C(MeasurementType.SIGNAL_STRENGTH, SignalCode.C, Frequency.I09),
/** Signal-strength IRNSS S B+C for Rinex3. */
- S9X(MeasurementType.SIGNAL_STRENGTH, Frequency.I09),
+ S9X(MeasurementType.SIGNAL_STRENGTH, SignalCode.X, Frequency.I09),
/** Signal-strength GPS L1 C/A / GLONASS G1 C/A for Rinex2. */
- SA(MeasurementType.SIGNAL_STRENGTH, Frequency.G01, Frequency.R01),
+ SA(MeasurementType.SIGNAL_STRENGTH, SignalCode.C, Frequency.G01, Frequency.R01),
/** Signal-strength GPS L1C for Rinex2. */
- SB(MeasurementType.SIGNAL_STRENGTH, Frequency.G01),
+ SB(MeasurementType.SIGNAL_STRENGTH, SignalCode.L, Frequency.G01),
/** Signal-strength GPS L2C for Rinex2. */
- SC(MeasurementType.SIGNAL_STRENGTH, Frequency.G02),
+ SC(MeasurementType.SIGNAL_STRENGTH, SignalCode.L, Frequency.G02),
/** Signal-strength GLONASS G2 for Rinex2. */
- SD(MeasurementType.SIGNAL_STRENGTH, Frequency.R02);
+ SD(MeasurementType.SIGNAL_STRENGTH, SignalCode.C, Frequency.R02);
/** Measurement type. */
private final MeasurementType type;
+ /** Signal code. */
+ private final SignalCode code;
+
/** Map of ferquencies. */
private final Map frequencies;
/** Simple constructor.
* @param type measurement type
+ * @param code signal code
* @param frequencies compatible frequencies
*/
- ObservationType(final MeasurementType type, final Frequency... frequencies) {
+ ObservationType(final MeasurementType type, final SignalCode code, final Frequency... frequencies) {
this.type = type;
+ this.code = code;
this.frequencies = new HashMap<>(frequencies.length);
for (final Frequency f : frequencies) {
this.frequencies.put(f.getSatelliteSystem(), f);
@@ -813,6 +902,13 @@ public enum ObservationType {
return type;
}
+ /** Get the signal code.
+ * @return signal code
+ */
+ public SignalCode getSignalCode() {
+ return code;
+ }
+
/** Get the frequency for a specified satellite system.
* @param system satellite system
* @return frequency for the satellite system, or null if satellite system not compatible
diff --git a/src/main/java/org/orekit/gnss/RinexLoader.java b/src/main/java/org/orekit/gnss/RinexLoader.java
index ec40540ecc7a06f3559c9deb023a17e69454c9ae..a17fb2390457d09d54d28fb551205b512b72c650 100644
--- a/src/main/java/org/orekit/gnss/RinexLoader.java
+++ b/src/main/java/org/orekit/gnss/RinexLoader.java
@@ -19,6 +19,7 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -40,7 +41,7 @@ import org.orekit.time.TimeScalesFactory;
/** Loader for Rinex measurements files.
*
* Supported versions are: 2.00, 2.10, 2.11, 2.12 (unofficial), 2.20 (unofficial),
- * 3.00, 3.01, 3.02, and 3.03.
+ * 3.00, 3.01, 3.02, 3.03, and 3.04.
*
* @see rinex 2.0
* @see rinex 2.10
@@ -51,6 +52,7 @@ import org.orekit.time.TimeScalesFactory;
* @see rinex 3.01
* @see rinex 3.02
* @see rinex 3.03
+ * @see rinex 3.04
* @since 9.2
*/
public class RinexLoader {
@@ -95,6 +97,7 @@ public class RinexLoader {
private static final String SYS_PCVS_APPLIED = "SYS / PCVS APPLIED";
private static final String SYS_SCALE_FACTOR = "SYS / SCALE FACTOR";
private static final String SYS_PHASE_SHIFT = "SYS / PHASE SHIFT";
+ private static final String SYS_PHASE_SHIFTS = "SYS / PHASE SHIFTS";
private static final String GLONASS_SLOT_FRQ_NB = "GLONASS SLOT / FRQ #";
private static final String GLONASS_COD_PHS_BIS = "GLONASS COD/PHS/BIS";
private static final String OBS_SCALE_FACTOR = "OBS SCALE FACTOR";
@@ -153,6 +156,15 @@ public class RinexLoader {
/** File type accepted (only Observation Data). */
private static final String FILE_TYPE = "O"; //Only Observation Data files
+ /** Name of the file. */
+ private String name;
+
+ /** Current line. */
+ private String line;
+
+ /** current line number. */
+ private int lineNumber;
+
/** {@inheritDoc} */
@Override
public boolean stillAcceptsData() {
@@ -162,16 +174,19 @@ public class RinexLoader {
/** {@inheritDoc} */
@Override
- public void loadData(final InputStream input, final String name)
+ public void loadData(final InputStream input, final String fileName)
throws IOException, OrekitException {
- try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8"))) {
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {
+
+ this.name = fileName;
+ this.line = null;
+ this.lineNumber = 0;
// placeholders for parsed data
SatelliteSystem satelliteSystem = null;
double formatVersion = Double.NaN;
boolean inRinexVersion = false;
- int lineNumber = 0;
SatelliteSystem obsTypesSystem = null;
String markerName = null;
String markerNumber = null;
@@ -204,14 +219,12 @@ public class RinexLoader {
int leapSeconds = 0;
AbsoluteDate tObs = AbsoluteDate.PAST_INFINITY;
String[] satsObsList = null;
- String strYear = null;
int eventFlag = -1;
int nbSatObs = -1;
int nbLinesSat = -1;
double rcvrClkOffset = 0;
boolean inRunBy = false;
boolean inMarkerName = false;
- boolean inMarkerType = false;
boolean inObserver = false;
boolean inRecType = false;
boolean inAntType = false;
@@ -229,22 +242,22 @@ public class RinexLoader {
final Map> listTypeObs = new HashMap<>();
//First line must always contain Rinex Version, File Type and Satellite Systems Observed
- String line = reader.readLine();
- lineNumber++;
- formatVersion = parseDouble(line, 0, 9);
+ readLine(reader, true);
+ formatVersion = parseDouble(0, 9);
int format100 = (int) FastMath.rint(100 * formatVersion);
if ((format100 != 200) && (format100 != 210) && (format100 != 211) &&
(format100 != 212) && (format100 != 220) && (format100 != 300) &&
- (format100 != 301) && (format100 != 302) && (format100 != 303)) {
+ (format100 != 301) && (format100 != 302) && (format100 != 303) &&
+ (format100 != 304)) {
throw new OrekitException(OrekitMessages.UNSUPPORTED_FILE_FORMAT, name);
}
//File Type must be Observation_Data
- if (!(parseString(line, 20, 1)).equals(FILE_TYPE)) {
+ if (!(parseString(20, 1)).equals(FILE_TYPE)) {
throw new OrekitException(OrekitMessages.UNSUPPORTED_FILE_FORMAT, name);
}
- satelliteSystem = SatelliteSystem.parseSatelliteSystem(parseString(line, 40, 1));
+ satelliteSystem = SatelliteSystem.parseSatelliteSystem(parseString(40, 1));
inRinexVersion = true;
switch (format100 / 100) {
@@ -256,19 +269,18 @@ public class RinexLoader {
final int MAX_OBS_TYPES_SCALE_FACTOR = 8;
final List typesObs = new ArrayList<>();
- for (line = reader.readLine(); line != null; line = reader.readLine()) {
- ++lineNumber;
+ while (readLine(reader, false)) {
if (rinexHeader == null) {
switch(line.substring(LABEL_START).trim()) {
case RINEX_VERSION_TYPE :
- formatVersion = parseDouble(line, 0, 9);
+ formatVersion = parseDouble(0, 9);
//File Type must be Observation_Data
- if (!(parseString(line, 20, 1)).equals(FILE_TYPE)) {
+ if (!(parseString(20, 1)).equals(FILE_TYPE)) {
throw new OrekitException(OrekitMessages.UNSUPPORTED_FILE_FORMAT, name);
}
- satelliteSystem = SatelliteSystem.parseSatelliteSystem(parseString(line, 40, 1));
+ satelliteSystem = SatelliteSystem.parseSatelliteSystem(parseString(40, 1));
inRinexVersion = true;
break;
case COMMENT :
@@ -278,68 +290,68 @@ public class RinexLoader {
inRunBy = true;
break;
case MARKER_NAME :
- markerName = parseString(line, 0, 60);
+ markerName = parseString(0, 60);
inMarkerName = true;
break;
case MARKER_NUMBER :
- markerNumber = parseString(line, 0, 20);
+ markerNumber = parseString(0, 20);
break;
case MARKER_TYPE :
- markerType = parseString(line, 0, 20);
+ markerType = parseString(0, 20);
break;
case OBSERVER_AGENCY :
- observerName = parseString(line, 0, 20);
- agencyName = parseString(line, 20, 40);
+ observerName = parseString(0, 20);
+ agencyName = parseString(20, 40);
inObserver = true;
break;
case REC_NB_TYPE_VERS :
- receiverNumber = parseString(line, 0, 20);
- receiverType = parseString(line, 20, 20);
- receiverVersion = parseString(line, 40, 20);
+ receiverNumber = parseString(0, 20);
+ receiverType = parseString(20, 20);
+ receiverVersion = parseString(40, 20);
inRecType = true;
break;
case ANT_NB_TYPE :
- antennaNumber = parseString(line, 0, 20);
- antennaType = parseString(line, 20, 20);
+ antennaNumber = parseString(0, 20);
+ antennaType = parseString(20, 20);
inAntType = true;
break;
case APPROX_POSITION_XYZ :
- approxPos = new Vector3D(parseDouble(line, 0, 14), parseDouble(line, 14, 14),
- parseDouble(line, 28, 14));
+ approxPos = new Vector3D(parseDouble(0, 14), parseDouble(14, 14),
+ parseDouble(28, 14));
inAproxPos = true;
break;
case ANTENNA_DELTA_H_E_N :
- antHeight = parseDouble(line, 0, 14);
- eccentricities = new Vector2D(parseDouble(line, 14, 14), parseDouble(line, 28, 14));
+ antHeight = parseDouble(0, 14);
+ eccentricities = new Vector2D(parseDouble(14, 14), parseDouble(28, 14));
inAntDelta = true;
break;
case ANTENNA_DELTA_X_Y_Z :
- antRefPoint = new Vector3D(parseDouble(line, 0, 14),
- parseDouble(line, 14, 14),
- parseDouble(line, 28, 14));
+ antRefPoint = new Vector3D(parseDouble(0, 14),
+ parseDouble(14, 14),
+ parseDouble(28, 14));
break;
case ANTENNA_B_SIGHT_XYZ :
- antBSight = new Vector3D(parseDouble(line, 0, 14),
- parseDouble(line, 14, 14),
- parseDouble(line, 28, 14));
+ antBSight = new Vector3D(parseDouble(0, 14),
+ parseDouble(14, 14),
+ parseDouble(28, 14));
break;
case CENTER_OF_MASS_XYZ :
- centerMass = new Vector3D(parseDouble(line, 0, 14),
- parseDouble(line, 14, 14),
- parseDouble(line, 28, 14));
+ centerMass = new Vector3D(parseDouble(0, 14),
+ parseDouble(14, 14),
+ parseDouble(28, 14));
break;
case NB_OF_SATELLITES :
- nbSat = parseInt(line, 0, 6);
+ nbSat = parseInt(0, 6);
break;
case WAVELENGTH_FACT_L1_2 :
//Optional line in header
//Not stored for now
break;
case RCV_CLOCK_OFFS_APPL :
- clkOffset = parseInt(line, 0, 6);
+ clkOffset = parseInt(0, 6);
break;
case INTERVAL :
- interval = parseDouble(line, 0, 10);
+ interval = parseDouble(0, 10);
break;
case TIME_OF_FIRST_OBS :
switch (satelliteSystem) {
@@ -354,7 +366,7 @@ public class RinexLoader {
break;
case MIXED:
//in Case of Mixed data, Timescale must be specified in the Time of First line
- timeScaleStr = parseString(line, 48, 3);
+ timeScaleStr = parseString(48, 3);
if (timeScaleStr.equals(GPS)) {
timeScale = TimeScalesFactory.getGPS();
@@ -371,60 +383,59 @@ public class RinexLoader {
lineNumber, name, line);
}
- tFirstObs = new AbsoluteDate(parseInt(line, 0, 6),
- parseInt(line, 6, 6),
- parseInt(line, 12, 6),
- parseInt(line, 18, 6),
- parseInt(line, 24, 6),
- parseDouble(line, 30, 13), timeScale);
+ tFirstObs = new AbsoluteDate(parseInt(0, 6),
+ parseInt(6, 6),
+ parseInt(12, 6),
+ parseInt(18, 6),
+ parseInt(24, 6),
+ parseDouble(30, 13), timeScale);
inFirstObs = true;
break;
case TIME_OF_LAST_OBS :
- tLastObs = new AbsoluteDate(parseInt(line, 0, 6),
- parseInt(line, 6, 6),
- parseInt(line, 12, 6),
- parseInt(line, 18, 6),
- parseInt(line, 24, 6),
- parseDouble(line, 30, 13), timeScale);
+ tLastObs = new AbsoluteDate(parseInt(0, 6),
+ parseInt(6, 6),
+ parseInt(12, 6),
+ parseInt(18, 6),
+ parseInt(24, 6),
+ parseDouble(30, 13), timeScale);
break;
case LEAP_SECONDS :
- leapSeconds = parseInt(line, 0, 6);
+ leapSeconds = parseInt(0, 6);
break;
case PRN_NB_OF_OBS :
//Optional line in header, indicates number of Observations par Satellite
//Not stored for now
break;
case NB_TYPES_OF_OBSERV :
- nbTypes = parseInt(line, 0, 6);
+ nbTypes = parseInt(0, 6);
final int nbLinesTypesObs = (nbTypes + MAX_OBS_TYPES_PER_LINE_RNX2 - 1 ) / MAX_OBS_TYPES_PER_LINE_RNX2;
for (int j = 0; j < nbLinesTypesObs; j++) {
if (j > 0) {
- line = reader.readLine(); //Next line
- lineNumber++;
+ readLine(reader, true);
}
final int iMax = FastMath.min(MAX_OBS_TYPES_PER_LINE_RNX2, nbTypes - typesObs.size());
for (int i = 0; i < iMax; i++) {
try {
- typesObs.add(ObservationType.valueOf(parseString(line, 10 + (6 * i), 2)));
+ typesObs.add(ObservationType.valueOf(parseString(10 + (6 * i), 2)));
} catch (IllegalArgumentException iae) {
throw new OrekitException(OrekitMessages.UNKNOWN_RINEX_FREQUENCY,
- parseString(line, 10 + (6 * i), 2), name, lineNumber);
+ parseString(10 + (6 * i), 2), name, lineNumber);
}
}
}
inTypesObs = true;
break;
case OBS_SCALE_FACTOR :
- scaleFactor = FastMath.max(1, parseInt(line, 0, 6));
- nbObsScaleFactor = parseInt(line, 6, 6);
+ scaleFactor = FastMath.max(1, parseInt(0, 6));
+ nbObsScaleFactor = parseInt(6, 6);
if (nbObsScaleFactor > MAX_OBS_TYPES_SCALE_FACTOR) {
throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
lineNumber, name, line);
}
final List typesObsScaleFactor = new ArrayList<>(nbObsScaleFactor);
for (int i = 0; i < nbObsScaleFactor; i++) {
- typesObsScaleFactor.add(ObservationType.valueOf(parseString(line, 16 + (6 * i), 2)));
+ typesObsScaleFactor.add(ObservationType.valueOf(parseString(16 + (6 * i), 2)));
}
scaleFactorCorrections.add(new ScaleFactorCorrection(satelliteSystem,
scaleFactor, typesObsScaleFactor));
@@ -464,43 +475,40 @@ public class RinexLoader {
nbSatObs = -1;
satsObsList = null;
tObs = null;
- strYear = null;
- eventFlag = parseInt(line, 28, 1);
+ eventFlag = parseInt(28, 1);
//If eventFlag>1, we skip the corresponding lines to the next observation
- if (eventFlag != 0) {
+ if (eventFlag > 1) {
if (eventFlag == 6) {
- nbSatObs = parseInt(line, 29, 3);
+ nbSatObs = parseInt(29, 3);
nbLinesSat = (nbSatObs + 12 - 1) / 12;
final int nbLinesObs = (nbTypes + 5 - 1) / 5;
final int nbLinesSkip = (nbLinesSat - 1) + nbSatObs * nbLinesObs;
for (int i = 0; i < nbLinesSkip; i++) {
- line = reader.readLine(); //Next line
- lineNumber++;
+ readLine(reader, true);
}
} else {
- final int nbLinesSkip = parseInt(line, 29, 3);
+ final int nbLinesSkip = parseInt(29, 3);
for (int i = 0; i < nbLinesSkip; i++) {
- line = reader.readLine(); //Next line
- lineNumber++;
+ readLine(reader, true);
}
}
} else {
- final int y = Integer.parseInt(parseString(line, 0, 3));
+ int y = parseInt(0, 3);
if (79 < y && y <= 99) {
- strYear = "19" + y;
+ y += 1900;
} else if (0 <= y && y <= 79) {
- strYear = "20" + parseString(line, 0, 3);
+ y += 2000;
}
- tObs = new AbsoluteDate(Integer.parseInt(strYear),
- parseInt(line, 3, 3),
- parseInt(line, 6, 3),
- parseInt(line, 9, 3),
- parseInt(line, 12, 3),
- parseDouble(line, 15, 11), timeScale);
-
- nbSatObs = parseInt(line, 29, 3);
+ tObs = new AbsoluteDate(y,
+ parseInt(3, 3),
+ parseInt(6, 3),
+ parseInt(9, 3),
+ parseInt(12, 3),
+ parseDouble(15, 11), timeScale);
+
+ nbSatObs = parseInt(29, 3);
satsObsList = new String[nbSatObs];
//If the total number of satellites was indicated in the Header
if (nbSat != -1 && nbSatObs > nbSat) {
@@ -512,16 +520,15 @@ public class RinexLoader {
nbLinesSat = (nbSatObs + MAX_N_SAT_OBSERVATION - 1) / MAX_N_SAT_OBSERVATION;
for (int j = 0; j < nbLinesSat; j++) {
if (j > 0) {
- line = reader.readLine(); //Next line
- lineNumber++;
+ readLine(reader, true);
}
final int iMax = FastMath.min(MAX_N_SAT_OBSERVATION, nbSatObs - j * MAX_N_SAT_OBSERVATION);
for (int i = 0; i < iMax; i++) {
- satsObsList[i + MAX_N_SAT_OBSERVATION * j] = parseString(line, 32 + 3 * i, 3);
+ satsObsList[i + MAX_N_SAT_OBSERVATION * j] = parseString(32 + 3 * i, 3);
}
//Read the Receiver Clock offset, if present
- rcvrClkOffset = parseDouble(line, 68, 12);
+ rcvrClkOffset = parseDouble(68, 12);
if (Double.isNaN(rcvrClkOffset)) {
rcvrClkOffset = 0.0;
}
@@ -538,12 +545,11 @@ public class RinexLoader {
// - 5 Observations per line
final List observationData = new ArrayList<>(nbSatObs);
for (int j = 0; j < nbLinesObs; j++) {
- line = reader.readLine(); //Next line
- lineNumber++;
+ readLine(reader, true);
final int iMax = FastMath.min(MAX_N_TYPES_OBSERVATION, nbTypes - observationData.size());
for (int i = 0; i < iMax; i++) {
final ObservationType type = typesObs.get(observationData.size());
- double value = parseDouble(line, 16 * i, 14);
+ double value = parseDouble(16 * i, 14);
boolean scaleFactorFound = false;
//We look for the lines of ScaledFactorCorrections
for (int l = 0; l < scaleFactorCorrections.size() && !scaleFactorFound; ++l) {
@@ -555,8 +561,8 @@ public class RinexLoader {
}
observationData.add(new ObservationData(type,
value,
- parseInt(line, 14 + 16 * i, 1),
- parseInt(line, 15 + 16 * i, 1)));
+ parseInt(14 + 16 * i, 1),
+ parseInt(15 + 16 * i, 1)));
}
}
@@ -625,21 +631,20 @@ public class RinexLoader {
ObservationType phaseShiftTypeObs = null;
- for (line = reader.readLine(); line != null; line = reader.readLine()) {
- ++lineNumber;
+ while (readLine(reader, false)) {
if (rinexHeader == null) {
switch(line.substring(LABEL_START).trim()) {
case RINEX_VERSION_TYPE : {
- formatVersion = parseDouble(line, 0, 9);
+ formatVersion = parseDouble(0, 9);
format100 = (int) FastMath.rint(100 * formatVersion);
- if ((format100 != 300) && (format100 != 301) && (format100 != 302) && (format100 != 303)) {
+ if ((format100 != 300) && (format100 != 301) && (format100 != 302) && (format100 != 303) && (format100 != 304)) {
throw new OrekitException(OrekitMessages.UNSUPPORTED_FILE_FORMAT, name);
}
//File Type must be Observation_Data
- if (!(parseString(line, 20, 1)).equals(FILE_TYPE)) {
+ if (!(parseString(20, 1)).equals(FILE_TYPE)) {
throw new OrekitException(OrekitMessages.UNSUPPORTED_FILE_FORMAT, name);
}
- satelliteSystem = SatelliteSystem.parseSatelliteSystem(parseString(line, 40, 1));
+ satelliteSystem = SatelliteSystem.parseSatelliteSystem(parseString(40, 1));
inRinexVersion = true;
}
break;
@@ -650,82 +655,81 @@ public class RinexLoader {
inRunBy = true;
break;
case MARKER_NAME :
- markerName = parseString(line, 0, 60);
+ markerName = parseString(0, 60);
inMarkerName = true;
break;
case MARKER_NUMBER :
- markerNumber = parseString(line, 0, 20);
+ markerNumber = parseString(0, 20);
break;
case MARKER_TYPE :
- markerType = parseString(line, 0, 20);
- inMarkerType = true;
+ markerType = parseString(0, 20);
//Could be done with an Enumeration
break;
case OBSERVER_AGENCY :
- observerName = parseString(line, 0, 20);
- agencyName = parseString(line, 20, 40);
+ observerName = parseString(0, 20);
+ agencyName = parseString(20, 40);
inObserver = true;
break;
case REC_NB_TYPE_VERS :
- receiverNumber = parseString(line, 0, 20);
- receiverType = parseString(line, 20, 20);
- receiverVersion = parseString(line, 40, 20);
+ receiverNumber = parseString(0, 20);
+ receiverType = parseString(20, 20);
+ receiverVersion = parseString(40, 20);
inRecType = true;
break;
case ANT_NB_TYPE :
- antennaNumber = parseString(line, 0, 20);
- antennaType = parseString(line, 20, 20);
+ antennaNumber = parseString(0, 20);
+ antennaType = parseString(20, 20);
inAntType = true;
break;
case APPROX_POSITION_XYZ :
- approxPos = new Vector3D(parseDouble(line, 0, 14),
- parseDouble(line, 14, 14),
- parseDouble(line, 28, 14));
+ approxPos = new Vector3D(parseDouble(0, 14),
+ parseDouble(14, 14),
+ parseDouble(28, 14));
inAproxPos = true;
break;
case ANTENNA_DELTA_H_E_N :
- antHeight = parseDouble(line, 0, 14);
- eccentricities = new Vector2D(parseDouble(line, 14, 14),
- parseDouble(line, 28, 14));
+ antHeight = parseDouble(0, 14);
+ eccentricities = new Vector2D(parseDouble(14, 14),
+ parseDouble(28, 14));
inAntDelta = true;
break;
case ANTENNA_DELTA_X_Y_Z :
- antRefPoint = new Vector3D(parseDouble(line, 0, 14),
- parseDouble(line, 14, 14),
- parseDouble(line, 28, 14));
+ antRefPoint = new Vector3D(parseDouble(0, 14),
+ parseDouble(14, 14),
+ parseDouble(28, 14));
break;
case ANTENNA_PHASECENTER :
- obsCode = parseString(line, 2, 3);
- antPhaseCenter = new Vector3D(parseDouble(line, 5, 9),
- parseDouble(line, 14, 14),
- parseDouble(line, 28, 14));
+ obsCode = parseString(2, 3);
+ antPhaseCenter = new Vector3D(parseDouble(5, 9),
+ parseDouble(14, 14),
+ parseDouble(28, 14));
break;
case ANTENNA_B_SIGHT_XYZ :
- antBSight = new Vector3D(parseDouble(line, 0, 14),
- parseDouble(line, 14, 14),
- parseDouble(line, 28, 14));
+ antBSight = new Vector3D(parseDouble(0, 14),
+ parseDouble(14, 14),
+ parseDouble(28, 14));
break;
case ANTENNA_ZERODIR_AZI :
- antAzi = parseDouble(line, 0, 14);
+ antAzi = parseDouble(0, 14);
break;
case ANTENNA_ZERODIR_XYZ :
- antZeroDir = new Vector3D(parseDouble(line, 0, 14),
- parseDouble(line, 14, 14),
- parseDouble(line, 28, 14));
+ antZeroDir = new Vector3D(parseDouble(0, 14),
+ parseDouble(14, 14),
+ parseDouble(28, 14));
break;
case CENTER_OF_MASS_XYZ :
- centerMass = new Vector3D(parseDouble(line, 0, 14),
- parseDouble(line, 14, 14),
- parseDouble(line, 28, 14));
+ centerMass = new Vector3D(parseDouble(0, 14),
+ parseDouble(14, 14),
+ parseDouble(28, 14));
break;
case NB_OF_SATELLITES :
- nbSat = parseInt(line, 0, 6);
+ nbSat = parseInt(0, 6);
break;
case RCV_CLOCK_OFFS_APPL :
- clkOffset = parseInt(line, 0, 6);
+ clkOffset = parseInt(0, 6);
break;
case INTERVAL :
- interval = parseDouble(line, 0, 10);
+ interval = parseDouble(0, 10);
break;
case TIME_OF_FIRST_OBS :
switch(satelliteSystem) {
@@ -749,7 +753,7 @@ public class RinexLoader {
break;
case MIXED:
//in Case of Mixed data, Timescale must be specified in the Time of First line
- timeScaleStr = parseString(line, 48, 3);
+ timeScaleStr = parseString(48, 3);
if (timeScaleStr.equals(GPS)) {
timeScale = TimeScalesFactory.getGPS();
@@ -772,27 +776,27 @@ public class RinexLoader {
lineNumber, name, line);
}
- tFirstObs = new AbsoluteDate(parseInt(line, 0, 6),
- parseInt(line, 6, 6),
- parseInt(line, 12, 6),
- parseInt(line, 18, 6),
- parseInt(line, 24, 6),
- parseDouble(line, 30, 13), timeScale);
+ tFirstObs = new AbsoluteDate(parseInt(0, 6),
+ parseInt(6, 6),
+ parseInt(12, 6),
+ parseInt(18, 6),
+ parseInt(24, 6),
+ parseDouble(30, 13), timeScale);
inFirstObs = true;
break;
case TIME_OF_LAST_OBS :
- tLastObs = new AbsoluteDate(parseInt(line, 0, 6),
- parseInt(line, 6, 6),
- parseInt(line, 12, 6),
- parseInt(line, 18, 6),
- parseInt(line, 24, 6),
- parseDouble(line, 30, 13), timeScale);
+ tLastObs = new AbsoluteDate(parseInt(0, 6),
+ parseInt(6, 6),
+ parseInt(12, 6),
+ parseInt(18, 6),
+ parseInt(24, 6),
+ parseDouble(30, 13), timeScale);
break;
case LEAP_SECONDS :
- leapSeconds = parseInt(line, 0, 6);
- leapSecondsFuture = parseInt(line, 6, 6);
- leapSecondsWeekNum = parseInt(line, 12, 6);
- leapSecondsDayNum = parseInt(line, 18, 6);
+ leapSeconds = parseInt(0, 6);
+ leapSecondsFuture = parseInt(6, 6);
+ leapSecondsWeekNum = parseInt(12, 6);
+ leapSecondsDayNum = parseInt(18, 6);
//Time System Identifier must be added, last A3 String
break;
case PRN_NB_OF_OBS :
@@ -803,22 +807,21 @@ public class RinexLoader {
obsTypesSystem = null;
typeObs.clear();
- obsTypesSystem = SatelliteSystem.parseSatelliteSystem(parseString(line, 0, 1));
- nbTypes = parseInt(line, 3, 3);
+ obsTypesSystem = SatelliteSystem.parseSatelliteSystem(parseString(0, 1));
+ nbTypes = parseInt(3, 3);
final int nbLinesTypesObs = (nbTypes + MAX_OBS_TYPES_PER_LINE_RNX3 - 1) / MAX_OBS_TYPES_PER_LINE_RNX3;
for (int j = 0; j < nbLinesTypesObs; j++) {
if (j > 0) {
- line = reader.readLine(); //Next line
- lineNumber++;
+ readLine(reader, true);
}
final int iMax = FastMath.min(MAX_OBS_TYPES_PER_LINE_RNX3, nbTypes - typeObs.size());
for (int i = 0; i < iMax; i++) {
try {
- typeObs.add(ObservationType.valueOf(parseString(line, 7 + (4 * i), 3)));
+ typeObs.add(ObservationType.valueOf(parseString(7 + (4 * i), 3)));
} catch (IllegalArgumentException iae) {
throw new OrekitException(OrekitMessages.UNKNOWN_RINEX_FREQUENCY,
- parseString(line, 7 + (4 * i), 3), name, lineNumber);
+ parseString(7 + (4 * i), 3), name, lineNumber);
}
}
}
@@ -826,26 +829,26 @@ public class RinexLoader {
inTypesObs = true;
break;
case SIGNAL_STRENGTH_UNIT :
- sigStrengthUnit = parseString(line, 0, 20);
+ sigStrengthUnit = parseString(0, 20);
break;
case SYS_DCBS_APPLIED :
- listAppliedDCBs.add(new AppliedDCBS(SatelliteSystem.parseSatelliteSystem(parseString(line, 0, 1)),
- parseString(line, 2, 17), parseString(line, 20, 40)));
+ listAppliedDCBs.add(new AppliedDCBS(SatelliteSystem.parseSatelliteSystem(parseString(0, 1)),
+ parseString(2, 17), parseString(20, 40)));
break;
case SYS_PCVS_APPLIED :
- listAppliedPCVS.add(new AppliedPCVS(SatelliteSystem.parseSatelliteSystem(parseString(line, 0, 1)),
- parseString(line, 2, 17), parseString(line, 20, 40)));
+ listAppliedPCVS.add(new AppliedPCVS(SatelliteSystem.parseSatelliteSystem(parseString(0, 1)),
+ parseString(2, 17), parseString(20, 40)));
break;
case SYS_SCALE_FACTOR :
satSystemScaleFactor = null;
scaleFactor = 1;
nbObsScaleFactor = 0;
- satSystemScaleFactor = SatelliteSystem.parseSatelliteSystem(parseString(line, 0, 1));
- scaleFactor = parseInt(line, 2, 4);
- nbObsScaleFactor = parseInt(line, 8, 2);
+ satSystemScaleFactor = SatelliteSystem.parseSatelliteSystem(parseString(0, 1));
+ scaleFactor = parseInt(2, 4);
+ nbObsScaleFactor = parseInt(8, 2);
final List typesObsScaleFactor = new ArrayList<>(nbObsScaleFactor);
if (nbObsScaleFactor == 0) {
@@ -855,12 +858,11 @@ public class RinexLoader {
MAX_OBS_TYPES_SCALE_FACTOR_PER_LINE;
for (int j = 0; j < nbLinesTypesObsScaleFactor; j++) {
if ( j > 0) {
- line = reader.readLine(); //Next line
- lineNumber++;
+ readLine(reader, true);
}
final int iMax = FastMath.min(MAX_OBS_TYPES_SCALE_FACTOR_PER_LINE, nbObsScaleFactor - typesObsScaleFactor.size());
for (int i = 0; i < iMax; i++) {
- typesObsScaleFactor.add(ObservationType.valueOf(parseString(line, 11 + (4 * i), 3)));
+ typesObsScaleFactor.add(ObservationType.valueOf(parseString(11 + (4 * i), 3)));
}
}
}
@@ -868,7 +870,8 @@ public class RinexLoader {
scaleFactorCorrections.add(new ScaleFactorCorrection(satSystemScaleFactor,
scaleFactor, typesObsScaleFactor));
break;
- case SYS_PHASE_SHIFT :
+ case SYS_PHASE_SHIFT :
+ case SYS_PHASE_SHIFTS : {
nbSatPhaseShift = 0;
satsPhaseShift = null;
@@ -876,10 +879,13 @@ public class RinexLoader {
phaseShiftTypeObs = null;
satSystemPhaseShift = null;
- satSystemPhaseShift = SatelliteSystem.parseSatelliteSystem(parseString(line, 0, 1));
- phaseShiftTypeObs = ObservationType.valueOf(parseString(line, 2, 3));
- nbSatPhaseShift = parseInt(line, 16, 2);
- corrPhaseShift = parseDouble(line, 6, 8);
+ satSystemPhaseShift = SatelliteSystem.parseSatelliteSystem(parseString(0, 1));
+ final String to = parseString(2, 3);
+ phaseShiftTypeObs = to.isEmpty() ?
+ null :
+ ObservationType.valueOf(to.length() < 3 ? "L" + to : to);
+ nbSatPhaseShift = parseInt(16, 2);
+ corrPhaseShift = parseDouble(6, 8);
if (nbSatPhaseShift == 0) {
//If nbSat with Phase Shift is not indicated: all the satellites are affected for this Obs Type
@@ -888,12 +894,11 @@ public class RinexLoader {
final int nbLinesSatPhaseShift = (nbSatPhaseShift + MAX_N_SAT_PHSHIFT_PER_LINE - 1) / MAX_N_SAT_PHSHIFT_PER_LINE;
for (int j = 0; j < nbLinesSatPhaseShift; j++) {
if (j > 0) {
- line = reader.readLine(); //Next line
- lineNumber++;
+ readLine(reader, true);
}
final int iMax = FastMath.min(MAX_N_SAT_PHSHIFT_PER_LINE, nbSatPhaseShift - j * MAX_N_SAT_PHSHIFT_PER_LINE);
for (int i = 0; i < iMax; i++) {
- satsPhaseShift[i + 10 * j] = parseString(line, 19 + 4 * i, 3);
+ satsPhaseShift[i + 10 * j] = parseString(19 + 4 * i, 3);
}
}
}
@@ -903,6 +908,7 @@ public class RinexLoader {
satsPhaseShift));
inPhaseShift = true;
break;
+ }
case GLONASS_SLOT_FRQ_NB :
//Not defined yet
inGlonassSlot = true;
@@ -914,7 +920,7 @@ public class RinexLoader {
case END_OF_HEADER :
//We make sure that we have read all the mandatory fields inside the header of the Rinex
if (!inRinexVersion || !inRunBy || !inMarkerName ||
- !inMarkerType || !inObserver || !inRecType || !inAntType ||
+ !inObserver || !inRecType || !inAntType ||
!inAproxPos || !inAntDelta || !inTypesObs || !inFirstObs ||
(formatVersion >= 3.01 && !inPhaseShift) ||
(formatVersion >= 3.03 && (!inGlonassSlot || !inGlonassCOD))) {
@@ -950,26 +956,25 @@ public class RinexLoader {
tObs = null;
//A line that starts with ">" correspond to a new observation epoch
- if (parseString(line, 0, 1).equals(">")) {
+ if (parseString(0, 1).equals(">")) {
- eventFlag = parseInt(line, 31, 1);
+ eventFlag = parseInt(31, 1);
//If eventFlag>1, we skip the corresponding lines to the next observation
if (eventFlag != 0) {
- final int nbLinesSkip = parseInt(line, 32, 3);
+ final int nbLinesSkip = parseInt(32, 3);
for (int i = 0; i < nbLinesSkip; i++) {
- line = reader.readLine();
- lineNumber++;
+ readLine(reader, true);
}
} else {
- tObs = new AbsoluteDate(parseInt(line, 2, 4),
- parseInt(line, 6, 3),
- parseInt(line, 9, 3),
- parseInt(line, 12, 3),
- parseInt(line, 15, 3),
- parseDouble(line, 18, 11), timeScale);
+ tObs = new AbsoluteDate(parseInt(2, 4),
+ parseInt(6, 3),
+ parseInt(9, 3),
+ parseInt(12, 3),
+ parseInt(15, 3),
+ parseDouble(18, 11), timeScale);
- nbSatObs = parseInt(line, 32, 3);
+ nbSatObs = parseInt(32, 3);
//If the total number of satellites was indicated in the Header
if (nbSat != -1 && nbSatObs > nbSat) {
//we check that the number of Sat in the observation is consistent
@@ -977,7 +982,7 @@ public class RinexLoader {
lineNumber, name, nbSatObs, nbSat);
}
//Read the Receiver Clock offset, if present
- rcvrClkOffset = parseDouble(line, 41, 15);
+ rcvrClkOffset = parseDouble(41, 15);
if (Double.isNaN(rcvrClkOffset)) {
rcvrClkOffset = 0.0;
}
@@ -985,11 +990,10 @@ public class RinexLoader {
//For each one of the Satellites in this Observation
for (int i = 0; i < nbSatObs; i++) {
- line = reader.readLine();
- lineNumber++;
+ readLine(reader, true);
//We check that the Satellite type is consistent with Satellite System in the top of the file
- final SatelliteSystem satelliteSystemSat = SatelliteSystem.parseSatelliteSystem(parseString(line, 0, 1));
+ final SatelliteSystem satelliteSystemSat = SatelliteSystem.parseSatelliteSystem(parseString(0, 1));
if (!satelliteSystem.equals(SatelliteSystem.MIXED)) {
if (!satelliteSystemSat.equals(satelliteSystem)) {
throw new OrekitException(OrekitMessages.INCONSISTENT_SATELLITE_SYSTEM,
@@ -997,7 +1001,7 @@ public class RinexLoader {
}
}
- final int prn = parseInt(line, 1, 2);
+ final int prn = parseInt(1, 2);
final int prnNumber;
switch (satelliteSystemSat) {
case GPS:
@@ -1024,7 +1028,7 @@ public class RinexLoader {
boolean scaleFactorFound = false;
//We look for the lines of ScaledFactorCorrections that correspond to this SatSystem
int k = 0;
- double value = parseDouble(line, 3 + j * 16, 14);
+ double value = parseDouble(3 + j * 16, 14);
while (k < scaleFactorCorrections.size() && !scaleFactorFound) {
if (scaleFactorCorrections.get(k).getSatelliteSystem().equals(satelliteSystemSat)) {
//We check if the next Observation Type to read needs to be scaled
@@ -1037,8 +1041,8 @@ public class RinexLoader {
}
observationData.add(new ObservationData(rf,
value,
- parseInt(line, 17 + j * 16, 1),
- parseInt(line, 18 + j * 16, 1)));
+ parseInt(17 + j * 16, 1),
+ parseInt(18 + j * 16, 1)));
}
observationDataSets.add(new ObservationDataSet(rinexHeader, satelliteSystemSat, prnNumber,
tObs, rcvrClkOffset, observationData));
@@ -1057,14 +1061,28 @@ public class RinexLoader {
}
}
+ /** Read a new line.
+ * @param reader reader from where to read line
+ * @param complainIfEnd if true an exception should be thrown if end of file is encountered
+ * @return true if a line has been read
+ * @exception IOException if a read error occurs
+ */
+ private boolean readLine(final BufferedReader reader, final boolean complainIfEnd)
+ throws IOException {
+ line = reader.readLine();
+ if (line == null && complainIfEnd) {
+ throw new OrekitException(OrekitMessages.UNEXPECTED_END_OF_FILE, name);
+ }
+ lineNumber++;
+ return line != null;
+ }
/** Extract a string from a line.
- * @param line to parse
* @param start start index of the string
* @param length length of the string
* @return parsed string
*/
- private String parseString(final String line, final int start, final int length) {
+ private String parseString(final int start, final int length) {
if (line.length() > start) {
return line.substring(start, FastMath.min(line.length(), start + length)).trim();
} else {
@@ -1073,28 +1091,26 @@ public class RinexLoader {
}
/** Extract an integer from a line.
- * @param line to parse
* @param start start index of the integer
* @param length length of the integer
* @return parsed integer
*/
- private int parseInt(final String line, final int start, final int length) {
- if (line.length() > start && !parseString(line, start, length).isEmpty()) {
- return Integer.parseInt(parseString(line, start, length));
+ private int parseInt(final int start, final int length) {
+ if (line.length() > start && !parseString(start, length).isEmpty()) {
+ return Integer.parseInt(parseString(start, length));
} else {
return 0;
}
}
/** Extract a double from a line.
- * @param line to parse
* @param start start index of the real
* @param length length of the real
* @return parsed real, or {@code Double.NaN} if field was empty
*/
- private double parseDouble(final String line, final int start, final int length) {
- if (line.length() > start && !parseString(line, start, length).isEmpty()) {
- return Double.parseDouble(parseString(line, start, length));
+ private double parseDouble(final int start, final int length) {
+ if (line.length() > start && !parseString(start, length).isEmpty()) {
+ return Double.parseDouble(parseString(start, length));
} else {
return Double.NaN;
}
@@ -1108,7 +1124,7 @@ public class RinexLoader {
/** Satellite System. */
private final SatelliteSystem satSystemPhaseShift;
- /** Carrier Phase Observation Code. */
+ /** Carrier Phase Observation Code (may be null). */
private final ObservationType typeObsPhaseShift;
/** Phase Shift Corrections (cycles). */
private final double phaseShiftCorrection;
@@ -1117,7 +1133,7 @@ public class RinexLoader {
/** Simple constructor.
* @param satSystemPhaseShift Satellite System
- * @param typeObsPhaseShift Carrier Phase Observation Code
+ * @param typeObsPhaseShift Carrier Phase Observation Code (may be null)
* @param phaseShiftCorrection Phase Shift Corrections (cycles)
* @param satsPhaseShift List of satellites involved
*/
@@ -1137,6 +1153,10 @@ public class RinexLoader {
return satSystemPhaseShift;
}
/** Get the Carrier Phase Observation Code.
+ *
+ * The observation code may be null for the uncorrected reference
+ * signal group
+ *
* @return Carrier Phase Observation Code.
*/
public ObservationType getTypeObs() {
diff --git a/src/main/java/org/orekit/gnss/SEMParser.java b/src/main/java/org/orekit/gnss/SEMParser.java
index 7ec930ba842a9342f28a1deeccd4943bc335e992..11caf260c8ff5a821b14e774093cb41065427dc4 100644
--- a/src/main/java/org/orekit/gnss/SEMParser.java
+++ b/src/main/java/org/orekit/gnss/SEMParser.java
@@ -20,6 +20,7 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
@@ -120,7 +121,7 @@ public class SEMParser implements DataLoader {
prnList.clear();
// Creates the reader
- final BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
+ final BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
try {
// Reads the number of almanacs in the file from the first line
diff --git a/src/main/java/org/orekit/gnss/SignalCode.java b/src/main/java/org/orekit/gnss/SignalCode.java
new file mode 100644
index 0000000000000000000000000000000000000000..fa8a33f900608139f7fee0bfa151872fb539c353
--- /dev/null
+++ b/src/main/java/org/orekit/gnss/SignalCode.java
@@ -0,0 +1,75 @@
+/* Copyright 2002-2019 CS Systèmes d'Information
+ * Licensed to CS Systèmes d'Information (CS) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.gnss;
+
+/**
+ * Enumerate for satellite signal code.
+ *
+ * @author Bryan Cazabonne
+ * @since 10.1
+ */
+public enum SignalCode {
+
+ /** Galileo A PRS / IRNSS A SPS / GLONASS L1OCd and L2CSI codes. */
+ A,
+
+ /** Galileo B I/NAV and B C/NAV / IRNSS B RS / GLONASS L1OCp and LO2Cp codes. */
+ B,
+
+ /** GPS C/A / GLONASS C/A / Galileo C / SBAS C/A / QZSS C/A / IRNSS C RS(P) codes. */
+ C,
+
+ /** GPS L1(C/A) + (P2-P1) / QZSS L5D codes. */
+ D,
+
+ /** QZSS L6E and L6 (D+E) codes. */
+ E,
+
+ /** GPS I / GLONASS I / Galileo I F/NAV, I I/NAV and I / SBAS I/ Beidou I codes. */
+ I,
+
+ /** GPS L1C (P) and L2C (L) / QZSS L1C (P), L2C (L) and L code. */
+ L,
+
+ /** GPS M code. */
+ M,
+
+ /** GPS P (AS off) / GLONASS P / QZSS L5P codes. */
+ P,
+
+ /** GPS Q / GLONASS Q / Galileo Q / SBAS Q / QZSS Q / Beidou Q codes. */
+ Q,
+
+ /** GPS L1C (D), L2C (M) / QZSS L1C (D), L2C (M) and S codes. */
+ S,
+
+ /** GPS Z - tracking and similar (AS off) / code. */
+ W,
+
+ /** GPS L1C (D+P), L2C (M+L) and I+Q / GLONASS I+Q, L1OCd+L1OCp and L2CSI+LO2Cp / Galileo B+C and I+Q / SBAS I+Q / QZSS L1C (D+P), L2C (M+L), I+Q and S+L / Beidou I+Q / IRNSS B+C codes. */
+ X,
+
+ /** GPS Y code. */
+ Y,
+
+ /** Galileo A+B+C / QZSS L1-SAIF, L5(D+P) and L6(D+E) codes. */
+ Z,
+
+ /** Codeless. */
+ CODELESS;
+
+}
diff --git a/src/main/java/org/orekit/gnss/YUMAParser.java b/src/main/java/org/orekit/gnss/YUMAParser.java
index 4e954d1d5f384d1ac58210674f4dc4ed6849ecfc..268a21a1b997c75b27a54cdbf8041669cc477850 100644
--- a/src/main/java/org/orekit/gnss/YUMAParser.java
+++ b/src/main/java/org/orekit/gnss/YUMAParser.java
@@ -20,6 +20,7 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
@@ -132,7 +133,7 @@ public class YUMAParser implements DataLoader {
prnList.clear();
// Creates the reader
- final BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
+ final BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
try {
// Gathers data to create one GPSAlmanac from 13 consecutive lines
diff --git a/src/main/java/org/orekit/gnss/antenna/AntexLoader.java b/src/main/java/org/orekit/gnss/antenna/AntexLoader.java
index c73c9aec21942fc28099de518cb53abf23cef26e..2b020456657534c7fb60ee77f14491dc103f4cc0 100644
--- a/src/main/java/org/orekit/gnss/antenna/AntexLoader.java
+++ b/src/main/java/org/orekit/gnss/antenna/AntexLoader.java
@@ -20,6 +20,7 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -155,7 +156,7 @@ public class AntexLoader {
public void loadData(final InputStream input, final String name)
throws IOException, OrekitException {
- try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8"))) {
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {
// placeholders for parsed data
int lineNumber = 0;
@@ -326,6 +327,13 @@ public class AntexLoader {
}
+ // Check if the number of frequencies has been parsed
+ if (patterns == null) {
+ // null object, an OrekitException is thrown
+ throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
+ lineNumber, name, line);
+ }
+
final PhaseCenterVariationFunction phaseCenterVariation;
if (grid2D == null) {
double max = 0;
diff --git a/src/main/java/org/orekit/gnss/attitude/GNSSAttitudeContext.java b/src/main/java/org/orekit/gnss/attitude/GNSSAttitudeContext.java
index f5046d90351c06484697c36d2f4031a913b6d60b..7e5255d66b0289732ff6e780dcc38919c25044fa 100644
--- a/src/main/java/org/orekit/gnss/attitude/GNSSAttitudeContext.java
+++ b/src/main/java/org/orekit/gnss/attitude/GNSSAttitudeContext.java
@@ -270,11 +270,11 @@ class GNSSAttitudeContext implements TimeStamped {
final double dtMin = FastMath.min(turnSpan.getTurnStartDate().durationFrom(date), dt0 - 60.0);
final double dtMax = FastMath.max(dtMin + fullTurn, dt0 + 60.0);
double[] bracket = UnivariateSolverUtils.bracket(yawReached, dt0,
- dtMin, dtMax, 1.0, 2.0, 15);
+ dtMin, dtMax, fullTurn / 100, 1.0, 100);
if (yawReached.value(bracket[0]) <= 0.0) {
// we have bracketed the wrong crossing
bracket = UnivariateSolverUtils.bracket(yawReached, 0.5 * (bracket[0] + bracket[1] + fullTurn),
- bracket[1], bracket[1] + fullTurn, 1.0, 2.0, 15);
+ bracket[1], bracket[1] + fullTurn, fullTurn / 100, 1.0, 100);
}
final double dt = new BracketingNthOrderBrentSolver(1.0e-3, 5).
solve(100, yawReached, bracket[0], bracket[1]);
diff --git a/src/main/java/org/orekit/gnss/attitude/GNSSFieldAttitudeContext.java b/src/main/java/org/orekit/gnss/attitude/GNSSFieldAttitudeContext.java
index dac0472a3d69c7f7e8f7595d09a87c07f10b0f1a..580260d87fdb7b8eb69305ebd470ea130d4e6c02 100644
--- a/src/main/java/org/orekit/gnss/attitude/GNSSFieldAttitudeContext.java
+++ b/src/main/java/org/orekit/gnss/attitude/GNSSFieldAttitudeContext.java
@@ -297,11 +297,11 @@ class GNSSFieldAttitudeContext> implements FieldTi
final double dtMin = FastMath.min(turnSpan.getTurnStartDate().durationFrom(date).getReal(), dt0 - 60.0);
final double dtMax = FastMath.max(dtMin + fullTurn, dt0 + 60.0);
double[] bracket = UnivariateSolverUtils.bracket(yawReached, dt0,
- dtMin, dtMax, 1.0, 2.0, 15);
+ dtMin, dtMax, fullTurn / 100, 1.0, 100);
if (yawReached.value(bracket[0]) <= 0.0) {
// we have bracketed the wrong crossing
bracket = UnivariateSolverUtils.bracket(yawReached, 0.5 * (bracket[0] + bracket[1] + fullTurn),
- bracket[1], bracket[1] + fullTurn, 1.0, 2.0, 15);
+ bracket[1], bracket[1] + fullTurn, fullTurn / 100, 1.0, 100);
}
final double dt = new BracketingNthOrderBrentSolver(1.0e-3, 5).
solve(100, yawReached, bracket[0], bracket[1]);
diff --git a/src/main/java/org/orekit/models/earth/GeoMagneticModelLoader.java b/src/main/java/org/orekit/models/earth/GeoMagneticModelLoader.java
index d79e2aa5e3be029821ff47d0d846c13f746b5f22..ca4cbb96f2a3698050d8c8c1e79f02abc38fc4a6 100644
--- a/src/main/java/org/orekit/models/earth/GeoMagneticModelLoader.java
+++ b/src/main/java/org/orekit/models/earth/GeoMagneticModelLoader.java
@@ -20,6 +20,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;
+import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.util.Collection;
import java.util.LinkedList;
@@ -93,7 +94,7 @@ public class GeoMagneticModelLoader implements DataLoader {
throws IOException, ParseException {
// open data file and parse values
- final StreamTokenizer str = new StreamTokenizer(new InputStreamReader(input, "UTF-8"));
+ final StreamTokenizer str = new StreamTokenizer(new InputStreamReader(input, StandardCharsets.UTF_8));
while (true) {
final GeoMagneticField model = readModel(str);
diff --git a/src/main/java/org/orekit/models/earth/ReferenceEllipsoid.java b/src/main/java/org/orekit/models/earth/ReferenceEllipsoid.java
index a1e6120605128b9b98e81778d2e17e42cba30d2f..c9b193eeb2db3b947af7ec4ada5655dcf84c52de 100644
--- a/src/main/java/org/orekit/models/earth/ReferenceEllipsoid.java
+++ b/src/main/java/org/orekit/models/earth/ReferenceEllipsoid.java
@@ -51,6 +51,7 @@ import org.orekit.utils.Constants;
* Third Edition, Amendment 1.
*
* @author Evan Ward
+ * @author Guylaine Prat
*/
public class ReferenceEllipsoid extends OneAxisEllipsoid implements EarthShape {
@@ -242,4 +243,42 @@ public class ReferenceEllipsoid extends OneAxisEllipsoid implements EarthShape {
);
}
+ /**
+ * Get the IERS96 ellipsoid, attached to the given body frame.
+ *
+ * @param bodyFrame the earth centered fixed frame
+ * @return an IERS96 reference ellipsoid
+ */
+ public static ReferenceEllipsoid getIers96(final Frame bodyFrame) {
+ return new ReferenceEllipsoid(Constants.IERS96_EARTH_EQUATORIAL_RADIUS,
+ Constants.IERS96_EARTH_FLATTENING, bodyFrame,
+ Constants.IERS96_EARTH_MU,
+ Constants.IERS96_EARTH_ANGULAR_VELOCITY);
+ }
+
+ /**
+ * Get the IERS2003 ellipsoid, attached to the given body frame.
+ *
+ * @param bodyFrame the earth centered fixed frame
+ * @return an IERS2003 reference ellipsoid
+ */
+ public static ReferenceEllipsoid getIers2003(final Frame bodyFrame) {
+ return new ReferenceEllipsoid(Constants.IERS2003_EARTH_EQUATORIAL_RADIUS,
+ Constants.IERS2003_EARTH_FLATTENING, bodyFrame,
+ Constants.IERS2003_EARTH_MU,
+ Constants.IERS2003_EARTH_ANGULAR_VELOCITY);
+ }
+
+ /**
+ * Get the IERS2010 ellipsoid, attached to the given body frame.
+ *
+ * @param bodyFrame the earth centered fixed frame
+ * @return an IERS2010 reference ellipsoid
+ */
+ public static ReferenceEllipsoid getIers2010(final Frame bodyFrame) {
+ return new ReferenceEllipsoid(Constants.IERS2010_EARTH_EQUATORIAL_RADIUS,
+ Constants.IERS2010_EARTH_FLATTENING, bodyFrame,
+ Constants.IERS2010_EARTH_MU,
+ Constants.IERS2010_EARTH_ANGULAR_VELOCITY);
+ }
}
diff --git a/src/main/java/org/orekit/models/earth/atmosphere/data/MarshallSolarActivityFutureEstimation.java b/src/main/java/org/orekit/models/earth/atmosphere/data/MarshallSolarActivityFutureEstimation.java
index 7895a50d0d3c35f67d7baf02b5d5851e79b8fe56..59b064ef656bbb5f36c8767d8573f3a03bf3c084 100644
--- a/src/main/java/org/orekit/models/earth/atmosphere/data/MarshallSolarActivityFutureEstimation.java
+++ b/src/main/java/org/orekit/models/earth/atmosphere/data/MarshallSolarActivityFutureEstimation.java
@@ -21,6 +21,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Serializable;
+import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.util.Iterator;
import java.util.SortedSet;
@@ -483,7 +484,7 @@ public class MarshallSolarActivityFutureEstimation implements DataLoader, DTM200
}
// read the data
- final BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
+ final BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
boolean inData = false;
final TimeScale utc = TimeScalesFactory.getUTC();
DateComponents fileDate = null;
diff --git a/src/main/java/org/orekit/models/earth/displacement/OceanLoadingCoefficientsBLQFactory.java b/src/main/java/org/orekit/models/earth/displacement/OceanLoadingCoefficientsBLQFactory.java
index 7464c16651c0d61244ba51ba954f1ecac378f036..1f2764e493a2294573f6332b54de5d1fe1dbcea6 100644
--- a/src/main/java/org/orekit/models/earth/displacement/OceanLoadingCoefficientsBLQFactory.java
+++ b/src/main/java/org/orekit/models/earth/displacement/OceanLoadingCoefficientsBLQFactory.java
@@ -20,6 +20,7 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@@ -285,7 +286,7 @@ public class OceanLoadingCoefficientsBLQFactory {
data[i][2] = new double[4];
}
- try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8"))) {
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {
int lineNumber = 0;
int dataLine = -1;
diff --git a/src/main/java/org/orekit/models/earth/ionosphere/FieldNeQuickParameters.java b/src/main/java/org/orekit/models/earth/ionosphere/FieldNeQuickParameters.java
new file mode 100644
index 0000000000000000000000000000000000000000..4c296e7921f493cb8e85ed2cde014c56b0380bee
--- /dev/null
+++ b/src/main/java/org/orekit/models/earth/ionosphere/FieldNeQuickParameters.java
@@ -0,0 +1,810 @@
+/* Copyright 2002-2019 CS Systèmes d'Information
+ * Licensed to CS Systèmes d'Information (CS) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.models.earth.ionosphere;
+
+import org.hipparchus.Field;
+import org.hipparchus.RealFieldElement;
+import org.hipparchus.util.FastMath;
+import org.hipparchus.util.FieldSinCos;
+import org.hipparchus.util.MathArrays;
+import org.orekit.time.DateComponents;
+import org.orekit.time.DateTimeComponents;
+import org.orekit.time.TimeComponents;
+
+/**
+ * This class perfoms the computation of the parameters used by the NeQuick model.
+ *
+ * @author Bryan Cazabonne
+ *
+ * @see "European Union (2016). European GNSS (Galileo) Open Service-Ionospheric Correction
+ * Algorithm for Galileo Single Frequency Users. 1.2."
+ *
+ * @since 10.1
+ */
+class FieldNeQuickParameters > {
+
+ /** Solar zenith angle at day night transition, degrees. */
+ private static final double X0 = 86.23292796211615;
+
+ /** F2 layer maximum density. */
+ private final T nmF2;
+
+ /** F2 layer maximum density height [km]. */
+ private final T hmF2;
+
+ /** F1 layer maximum density height [km]. */
+ private final T hmF1;
+
+ /** E layer maximum density height [km]. */
+ private final T hmE;
+
+ /** F2 layer bottom thickness parameter [km]. */
+ private final T b2Bot;
+
+ /** F1 layer top thickness parameter [km]. */
+ private final T b1Top;
+
+ /** F1 layer bottom thickness parameter [km]. */
+ private final T b1Bot;
+
+ /** E layer top thickness parameter [km]. */
+ private final T beTop;
+
+ /** E layer bottom thickness parameter [km]. */
+ private final T beBot;
+
+ /** topside thickness parameter [km]. */
+ private final T h0;
+
+ /** Layer amplitudes. */
+ private final T[] amplitudes;
+
+ /**
+ * Build a new instance.
+ * @param field field of the elements
+ * @param dateTime current date time components
+ * @param f2 F2 coefficients used by the F2 layer
+ * @param fm3 Fm3 coefficients used by the F2 layer
+ * @param latitude latitude of a point along the integration path, in radians
+ * @param longitude longitude of a point along the integration path, in radians
+ * @param alpha effective ionisation level coefficients
+ * @param modipGrip modip grid
+ */
+ FieldNeQuickParameters(final Field field, final DateTimeComponents dateTime, final double[][][] f2,
+ final double[][][] fm3, final T latitude, final T longitude,
+ final double[] alpha, final double[][] modipGrip) {
+
+ // Zero
+ final T zero = field.getZero();
+
+ // MODIP in degrees
+ final T modip = computeMODIP(latitude, longitude, modipGrip);
+ // Effective ionisation level Az
+ final T az = computeAz(modip, alpha);
+ // Effective sunspot number (Eq. 19)
+ final T azr = FastMath.sqrt(az.subtract(63.7).multiply(1123.6).add(167273.0)).subtract(408.99);
+ // Date and Time components
+ final DateComponents date = dateTime.getDate();
+ final TimeComponents time = dateTime.getTime();
+ // Hours
+ final double hours = time.getSecondsInUTCDay() / 3600.0;
+ // Effective solar zenith angle in radians
+ final T xeff = computeEffectiveSolarAngle(date.getMonth(), hours, latitude, longitude);
+
+ // Coefficients for F2 layer parameters
+ // Compute the array of interpolated coefficients for foF2 (Eq. 44)
+ final T[][] af2 = MathArrays.buildArray(field, 76, 13);
+ for (int j = 0; j < 76; j++) {
+ for (int k = 0; k < 13; k++ ) {
+ af2[j][k] = azr.multiply(0.01).negate().add(1.0).multiply(f2[0][j][k]).add(azr.multiply(0.01).multiply(f2[1][j][k]));
+ }
+ }
+
+ // Compute the array of interpolated coefficients for M(3000)F2 (Eq. 46)
+ final T[][] am3 = MathArrays.buildArray(field, 49, 9);
+ for (int j = 0; j < 49; j++) {
+ for (int k = 0; k < 9; k++ ) {
+ am3[j][k] = azr.multiply(0.01).negate().add(1.0).multiply(fm3[0][j][k]).add(azr.multiply(0.01).multiply(fm3[1][j][k]));
+ }
+ }
+
+ // E layer maximum density height in km (Eq. 78)
+ this.hmE = field.getZero().add(120.0);
+ // E layer critical frequency in MHz
+ final T foE = computefoE(date.getMonth(), az, xeff, latitude);
+ // E layer maximum density in 10^11 m-3 (Eq. 36)
+ final T nmE = foE.multiply(foE).multiply(0.124);
+
+ // Time argument (Eq. 49)
+ final double t = FastMath.toRadians(15 * hours) - FastMath.PI;
+ // Compute Fourier time series for foF2 and M(3000)F2
+ final T[] cf2 = computeCF2(field, af2, t);
+ final T[] cm3 = computeCm3(field, am3, t);
+ // F2 layer critical frequency in MHz
+ final T foF2 = computefoF2(field, modip, cf2, latitude, longitude);
+ // Maximum Usable Frequency factor
+ final T mF2 = computeMF2(field, modip, cm3, latitude, longitude);
+ // F2 layer maximum density in 10^11 m-3
+ this.nmF2 = foF2.multiply(foF2).multiply(0.124);
+ // F2 layer maximum density height in km
+ this.hmF2 = computehmF2(field, foE, foF2, mF2);
+
+ // F1 layer critical frequency in MHz
+ final T foF1 = computefoF1(field, foE, foF2);
+ // F1 layer maximum density in 10^11 m-3
+ final T nmF1;
+ if (foF1.getReal() <= 0.0 && foE.getReal() > 2.0) {
+ final T foEpopf = foE.add(0.5);
+ nmF1 = foEpopf.multiply(foEpopf).multiply(0.124);
+ } else {
+ nmF1 = foF1.multiply(foF1).multiply(0.124);
+ }
+ // F1 layer maximum density height in km
+ this.hmF1 = hmF2.add(hmE).multiply(0.5);
+
+ // Thickness parameters (Eq. 85 to 89)
+ final T a = clipExp(FastMath.log(foF2.multiply(foF2)).multiply(0.857).add(FastMath.log(mF2).multiply(2.02)).add(-3.467)).multiply(0.01);
+ this.b2Bot = nmF2.divide(a).multiply(0.385);
+ this.b1Top = hmF2.subtract(hmF1).multiply(0.3);
+ this.b1Bot = hmF1.subtract(hmE).multiply(0.5);
+ this.beTop = FastMath.max(b1Bot, zero.add(7.0));
+ this.beBot = zero.add(5.0);
+
+ // Layer amplitude coefficients
+ this.amplitudes = computeLayerAmplitudes(field, nmE, nmF1, foF1);
+
+ // Topside thickness parameter
+ this.h0 = computeH0(field, date.getMonth(), azr);
+ }
+
+ /**
+ * Get the F2 layer maximum density.
+ * @return nmF2
+ */
+ public T getNmF2() {
+ return nmF2;
+ }
+
+ /**
+ * Get the F2 layer maximum density height.
+ * @return hmF2 in km
+ */
+ public T getHmF2() {
+ return hmF2;
+ }
+
+ /**
+ * Get the F1 layer maximum density height.
+ * @return hmF1 in km
+ */
+ public T getHmF1() {
+ return hmF1;
+ }
+
+ /**
+ * Get the E layer maximum density height.
+ * @return hmE in km
+ */
+ public T getHmE() {
+ return hmE;
+ }
+
+ /**
+ * Get the F2 layer thickness parameter (bottom).
+ * @return B2Bot in km
+ */
+ public T getB2Bot() {
+ return b2Bot;
+ }
+
+ /**
+ * Get the F1 layer thickness parameter (top).
+ * @return B1Top in km
+ */
+ public T getB1Top() {
+ return b1Top;
+ }
+
+ /**
+ * Get the F1 layer thickness parameter (bottom).
+ * @return B1Bot in km
+ */
+ public T getB1Bot() {
+ return b1Bot;
+ }
+
+ /**
+ * Get the E layer thickness parameter (bottom).
+ * @return BeBot in km
+ */
+ public T getBEBot() {
+ return beBot;
+ }
+
+ /**
+ * Get the E layer thickness parameter (top).
+ * @return BeTop in km
+ */
+ public T getBETop() {
+ return beTop;
+ }
+
+ /**
+ * Get the F2, F1 and E layer amplitudes.
+ *
+ * The resulting element is an array having the following form:
+ *
+ * - double[0] = A1 → F2 layer amplitude
+ *
- double[1] = A2 → F1 layer amplitude
+ *
- double[2] = A3 → E layer amplitude
+ *
+ * @return layer amplitudes
+ */
+ public T[] getLayerAmplitudes() {
+ return amplitudes.clone();
+ }
+
+ /**
+ * Get the topside thickness parameter H0.
+ * @return H0 in km
+ */
+ public T getH0() {
+ return h0;
+ }
+
+ /**
+ * Computes the value of the modified dip latitude (MODIP) for the
+ * given latitude and longitude.
+ *
+ * @param lat receiver latitude, radians
+ * @param lon receiver longitude, radians
+ * @param stModip modip grid
+ * @return the MODIP in degrees
+ */
+ private T computeMODIP(final T lat, final T lon, final double[][] stModip) {
+
+ // Zero
+ final T zero = lat.getField().getZero();
+
+ // For the MODIP computation, the latitude and longitude have to be converted in degrees
+ final T latitude = FastMath.toDegrees(lat);
+ final T longitude = FastMath.toDegrees(lon);
+
+ // Extreme cases
+ if (latitude.getReal() == 90.0 || latitude.getReal() == -90.0) {
+ return latitude;
+ }
+
+ // Auxiliary parameter l (Eq. 6 to 8)
+ final int lF = (int) ((longitude.getReal() + 180) * 0.1);
+ int l = lF - 2;
+ if (l < 0) {
+ l += 36;
+ } else if (l > 33) {
+ l -= 36;
+ }
+
+ // Auxiliary parameter a (Eq. 9 to 11)
+ final T a = latitude.add(90).multiply(0.2).add(1.0);
+ final T aF = FastMath.floor(a);
+ // Eq. 10
+ final T x = a.subtract(aF);
+ // Eq. 11
+ final int i = (int) aF.getReal() - 2;
+
+ // zi coefficients (Eq. 12 and 13)
+ final T z1 = interpolate(zero.add(stModip[i + 1][l + 2]), zero.add(stModip[i + 2][l + 2]),
+ zero.add(stModip[i + 3][l + 2]), zero.add(stModip[i + 4][l + 2]), x);
+ final T z2 = interpolate(zero.add(stModip[i + 1][l + 3]), zero.add(stModip[i + 2][l + 3]),
+ zero.add(stModip[i + 3][l + 3]), zero.add(stModip[i + 4][l + 3]), x);
+ final T z3 = interpolate(zero.add(stModip[i + 1][l + 4]), zero.add(stModip[i + 2][l + 4]),
+ zero.add(stModip[i + 3][l + 4]), zero.add(stModip[i + 4][l + 4]), x);
+ final T z4 = interpolate(zero.add(stModip[i + 1][l + 5]), zero.add(stModip[i + 2][l + 5]),
+ zero.add(stModip[i + 3][l + 5]), zero.add(stModip[i + 4][l + 5]), x);
+
+ // Auxiliary parameter b (Eq. 14 and 15)
+ final T b = longitude.add(180).multiply(0.1);
+ final T bF = FastMath.floor(b);
+ final T y = b.subtract(bF);
+
+ // MODIP (Ref Eq. 16)
+ final T modip = interpolate(z1, z2, z3, z4, y);
+
+ return modip;
+ }
+
+ /**
+ * This method computes the effective ionisation level Az.
+ *
+ * This parameter is used for the computation of the Total Electron Content (TEC).
+ *
+ * @param modip modified dip latitude (MODIP) in degrees
+ * @param alpha effective ionisation level coefficients
+ * @return the ionisation level Az
+ */
+ private T computeAz(final T modip, final double[] alpha) {
+ // Field
+ final Field field = modip.getField();
+ // Zero
+ final T zero = field.getZero();
+ // Particular condition (Eq. 17)
+ if (alpha[0] == 0.0 && alpha[1] == 0.0 && alpha[2] == 0.0) {
+ return zero.add(63.7);
+ }
+ // Az = a0 + modip * a1 + modip^2 * a2 (Eq. 18)
+ T az = modip.multiply(alpha[2]).add(alpha[1]).multiply(modip).add(alpha[0]);
+ // If Az < 0 -> Az = 0
+ az = FastMath.max(zero, az);
+ // If Az > 400 -> Az = 400
+ az = FastMath.min(zero.add(400.0), az);
+ return az;
+ }
+
+ /**
+ * This method computes the effective solar zenith angle.
+ *
+ * The effective solar zenith angle is compute as a function of the
+ * solar zenith angle and the solar zenith angle at day night transition.
+ *
+ * @param month current month of the year
+ * @param hours universal time (hours)
+ * @param latitude in radians
+ * @param longitude in radians
+ * @return the effective solar zenith angle, radians
+ */
+ private T computeEffectiveSolarAngle(final int month,
+ final double hours,
+ final T latitude,
+ final T longitude) {
+ // Zero
+ final T zero = latitude.getField().getZero();
+ // Local time (Eq.4)
+ final T lt = longitude.divide(FastMath.toRadians(15.0)).add(hours);
+ // Day of year at the middle of the month (Eq. 20)
+ final double dy = 30.5 * month - 15.0;
+ // Time (Eq. 21)
+ final double t = dy + (18 - hours) / 24;
+ // Arguments am and al (Eq. 22 and 23)
+ final double am = FastMath.toRadians(0.9856 * t - 3.289);
+ final double al = am + FastMath.toRadians(1.916 * FastMath.sin(am) + 0.020 * FastMath.sin(2.0 * am) + 282.634);
+ // Sine and cosine of solar declination (Eq. 24 and 25)
+ final double sDec = 0.39782 * FastMath.sin(al);
+ final double cDec = FastMath.sqrt(1. - sDec * sDec);
+ // Solar zenith angle, deg (Eq. 26 and 27)
+ final FieldSinCos scLat = FastMath.sinCos(latitude);
+ final T coef = lt.negate().add(12.0).multiply(FastMath.PI / 12);
+ final T cZenith = scLat.sin().multiply(sDec).add(scLat.cos().multiply(cDec).multiply(FastMath.cos(coef)));
+ final T angle = FastMath.atan2(FastMath.sqrt(cZenith.multiply(cZenith).negate().add(1.0)), cZenith);
+ final T x = FastMath.toDegrees(angle);
+ // Effective solar zenith angle (Eq. 28)
+ final T xeff = join(clipExp(x.multiply(0.2).negate().add(20.0)).multiply(0.24).negate().add(90.0), x, zero.add(12.0), x.subtract(X0));
+ return FastMath.toRadians(xeff);
+ }
+
+ /**
+ * This method computes the E layer critical frequency at a given location.
+ * @param month current month
+ * @param az ffective ionisation level
+ * @param xeff effective solar zenith angle in radians
+ * @param latitude latitude in radians
+ * @return the E layer critical frequency at a given location in MHz
+ */
+ private T computefoE(final int month, final T az,
+ final T xeff, final T latitude) {
+ // The latitude has to be converted in degrees
+ final T lat = FastMath.toDegrees(latitude);
+ // Square root of the effective ionisation level
+ final T sqAz = FastMath.sqrt(az);
+ // seas parameter (Eq. 30 to 32)
+ final int seas;
+ if (month == 1 || month == 2 || month == 11 || month == 12) {
+ seas = -1;
+ } else if (month == 3 || month == 4 || month == 9 || month == 10) {
+ seas = 0;
+ } else {
+ seas = 1;
+ }
+ // Latitudinal dependence (Eq. 33 and 34)
+ final T ee = clipExp(lat.multiply(0.3));
+ final T seasp = ee.subtract(1.0).divide(ee.add(1.0)).multiply(seas);
+ // Critical frequency (Eq. 35)
+ final T coef = seasp.multiply(0.019).negate().add(1.112);
+ final T foE = FastMath.sqrt(coef .multiply(coef).multiply(sqAz).multiply(FastMath.cos(xeff).pow(0.6)).add(0.49));
+ return foE;
+ }
+
+ /**
+ * Computes the F2 layer height of maximum electron density.
+ * @param field field of the elements
+ * @param foE E layer layer critical frequency in MHz
+ * @param foF2 F2 layer layer critical frequency in MHz
+ * @param mF2 maximum usable frequency factor
+ * @return hmF2 in km
+ */
+ private T computehmF2(final Field field, final T foE, final T foF2, final T mF2) {
+ // Zero
+ final T zero = field.getZero();
+ // Ratio
+ final T fo = foF2.divide(foE);
+ final T ratio = join(fo, zero.add(1.75), zero.add(20.0), fo.subtract(1.75));
+
+ // deltaM parameter
+ T deltaM = zero.subtract(0.012);
+ if (foE.getReal() >= 1e-30) {
+ deltaM = deltaM.add(ratio.subtract(1.215).divide(0.253).reciprocal());
+ }
+
+ // hmF2 Eq. 80
+ final T mF2Sq = mF2.multiply(mF2);
+ final T temp = FastMath.sqrt(mF2Sq.multiply(0.0196).add(1.0).divide(mF2Sq.multiply(1.2967).subtract(1.0)));
+ final T height = mF2.multiply(1490.0).multiply(temp).divide(mF2.add(deltaM)).subtract(176.0);
+ return height;
+ }
+
+ /**
+ * Computes cf2 coefficients.
+ * @param field field of the elements
+ * @param af2 interpolated coefficients for foF2
+ * @param t time argument
+ * @return the cf2 coefficients array
+ */
+ private T[] computeCF2(final Field field, final T[][] af2, final double t) {
+ // Eq. 50
+ final T[] cf2 = MathArrays.buildArray(field, 76);
+ for (int i = 0; i < cf2.length; i++) {
+ T sum = field.getZero();
+ for (int k = 0; k < 6; k++) {
+ sum = sum.add(af2[i][2 * k + 1].multiply(FastMath.sin((k + 1) * t)).add(af2[i][2 * (k + 1)].multiply(FastMath.cos((k + 1) * t))));
+ }
+ cf2[i] = af2[i][0].add(sum);
+ }
+ return cf2;
+ }
+
+ /**
+ * Computes Cm3 coefficients.
+ * @param field field of the elements
+ * @param am3 interpolated coefficients for foF2
+ * @param t time argument
+ * @return the Cm3 coefficients array
+ */
+ private T[] computeCm3(final Field field, final T[][] am3, final double t) {
+ // Eq. 51
+ final T[] cm3 = MathArrays.buildArray(field, 49);
+ for (int i = 0; i < cm3.length; i++) {
+ T sum = field.getZero();
+ for (int k = 0; k < 4; k++) {
+ sum = sum.add(am3[i][2 * k + 1].multiply(FastMath.sin((k + 1) * t)).add(am3[i][2 * (k + 1)].multiply(FastMath.cos((k + 1) * t))));
+ }
+ cm3[i] = am3[i][0].add(sum);
+ }
+ return cm3;
+ }
+
+ /**
+ * This method computes the F2 layer critical frequency.
+ * @param field field of the elements
+ * @param modip modified DIP latitude, in degrees
+ * @param cf2 Fourier time series for foF2
+ * @param latitude latitude in radians
+ * @param longitude longitude in radians
+ * @return the F2 layer critical frequency, MHz
+ */
+ private T computefoF2(final Field field, final T modip, final T[] cf2,
+ final T latitude, final T longitude) {
+
+ // One
+ final T one = field.getOne();
+
+ // Legendre grades (Eq. 63)
+ final int[] q = new int[] {
+ 12, 12, 9, 5, 2, 1, 1, 1, 1
+ };
+
+ // Array for geographic terms
+ final T[] g = MathArrays.buildArray(field, cf2.length);
+ g[0] = one;
+
+ // MODIP coefficients Eq. 57
+ final T sinMODIP = FastMath.sin(FastMath.toRadians(modip));
+ final T[] m = MathArrays.buildArray(field, 12);
+ m[0] = one;
+ for (int i = 1; i < q[0]; i++) {
+ m[i] = sinMODIP.multiply(m[i - 1]);
+ g[i] = m[i];
+ }
+
+ // Latitude coefficients (Eq. 58)
+ final T cosLat = FastMath.cos(latitude);
+ final T[] p = MathArrays.buildArray(field, 8);
+ p[0] = cosLat;
+ for (int n = 2; n < 9; n++) {
+ p[n - 1] = cosLat.multiply(p[n - 2]);
+ }
+
+ // latitude and longitude terms
+ int index = 12;
+ for (int i = 1; i < q.length; i++) {
+ for (int j = 0; j < q[i]; j++) {
+ g[index++] = m[j].multiply(p[i - 1]).multiply(FastMath.cos(longitude.multiply(i)));
+ g[index++] = m[j].multiply(p[i - 1]).multiply(FastMath.sin(longitude.multiply(i)));
+ }
+ }
+
+ // Compute foF2 by linear combination
+ final T frequency = one.linearCombination(g, cf2);
+ return frequency;
+ }
+
+ /**
+ * This method computes the Maximum Usable Frequency factor.
+ * @param field field of the elements
+ * @param modip modified DIP latitude, in degrees
+ * @param cm3 Fourier time series for M(3000)F2
+ * @param latitude latitude in radians
+ * @param longitude longitude in radians
+ * @return the Maximum Usable Frequency factor
+ */
+ private T computeMF2(final Field field, final T modip, final T[] cm3,
+ final T latitude, final T longitude) {
+
+ // One
+ final T one = field.getOne();
+ // Legendre grades (Eq. 71)
+ final int[] r = new int[] {
+ 7, 8, 6, 3, 2, 1, 1
+ };
+
+ // Array for geographic terms
+ final T[] g = MathArrays.buildArray(field, cm3.length);
+ g[0] = one;
+
+ // MODIP coefficients Eq. 57
+ final T sinMODIP = FastMath.sin(FastMath.toRadians(modip));
+ final T[] m = MathArrays.buildArray(field, 12);
+ m[0] = one;
+ for (int i = 1; i < 12; i++) {
+ m[i] = sinMODIP.multiply(m[i - 1]);
+ if (i < 7) {
+ g[i] = m[i];
+ }
+ }
+
+ // Latitude coefficients (Eq. 58)
+ final T cosLat = FastMath.cos(latitude);
+ final T[] p = MathArrays.buildArray(field, 8);
+ p[0] = cosLat;
+ for (int n = 2; n < 9; n++) {
+ p[n - 1] = cosLat.multiply(p[n - 2]);
+ }
+
+ // latitude and longitude terms
+ int index = 7;
+ for (int i = 1; i < r.length; i++) {
+ for (int j = 0; j < r[i]; j++) {
+ g[index++] = m[j].multiply(p[i - 1]).multiply(FastMath.cos(longitude.multiply(i)));
+ g[index++] = m[j].multiply(p[i - 1]).multiply(FastMath.sin(longitude.multiply(i)));
+ }
+ }
+
+ // Compute m3000 by linear combination
+ final T m3000 = one.linearCombination(g, cm3);
+ return m3000;
+ }
+
+ /**
+ * This method computes the F1 layer critical frequency.
+ *
+ * This computation performs the algorithm exposed in Annex F
+ * of the reference document.
+ *
+ * @param field field of the elements
+ * @param foE the E layer critical frequency, MHz
+ * @return the F1 layer critical frequency, MHz
+ * @param foF2 the F2 layer critical frequency, MHz
+ */
+ private T computefoF1(final Field field, final T foE, final T foF2) {
+ final T zero = field.getZero();
+ final T temp = join(foE.multiply(1.4), zero, zero.add(1000.0), foE.subtract(2.0));
+ final T temp2 = join(zero, temp, zero.add(1000.0), foE.subtract(temp));
+ final T value = join(temp2, temp2.multiply(0.85), zero.add(60.0), foF2.multiply(0.85).subtract(temp2));
+ if (value.getReal() < 1.0E-6) {
+ return zero;
+ } else {
+ return value;
+ }
+ }
+
+ /**
+ * This method allows the computation of the F2, F1 and E layer amplitudes.
+ *
+ * The resulting element is an array having the following form:
+ *
+ * - double[0] = A1 → F2 layer amplitude
+ *
- double[1] = A2 → F1 layer amplitude
+ *
- double[2] = A3 → E layer amplitude
+ *
+ *
+ * @param field field of the elements
+ * @param nmE E layer maximum density in 10^11 m-3
+ * @param nmF1 F1 layer maximum density in 10^11 m-3
+ * @param foF1 F1 layer critical frequency in MHz
+ * @return a three components array containing the layer amplitudes
+ */
+ private T[] computeLayerAmplitudes(final Field field, final T nmE, final T nmF1, final T foF1) {
+ // Zero
+ final T zero = field.getZero();
+
+ // Initialize array
+ final T[] amplitude = MathArrays.buildArray(field, 3);
+
+ // F2 layer amplitude (Eq. 90)
+ final T a1 = nmF2.multiply(4.0);
+ amplitude[0] = a1;
+
+ // F1 and E layer amplitudes (Eq. 91 to 98)
+ if (foF1.getReal() < 0.5) {
+ amplitude[1] = zero;
+ amplitude[2] = nmE.subtract(epst(a1, hmF2, b2Bot, hmE)).multiply(4.0);
+ } else {
+ T a2a = zero;
+ T a3a = nmE.multiply(4.0);
+ for (int i = 0; i < 5; i++) {
+ a2a = nmF1.subtract(epst(a1, hmF2, b2Bot, hmF1)).subtract(epst(a3a, hmE, beTop, hmF1)).multiply(4.0);
+ a2a = join(a2a, nmF1.multiply(0.8), field.getOne(), a2a.subtract(nmF1.multiply(0.8)));
+ a3a = nmE.subtract(epst(a2a, hmF1, b1Bot, hmE)).subtract(epst(a1, hmF2, b2Bot, hmE)).multiply(4.0);
+ }
+ amplitude[1] = a2a;
+ amplitude[2] = join(a3a, zero.add(0.05), zero.add(60.0), a3a.subtract(0.005));
+ }
+
+ return amplitude;
+ }
+
+ /**
+ * This method computes the topside thickness parameter H0.
+ *
+ * @param field field of the elements
+ * @param month current month
+ * @param azr effective sunspot number
+ * @return H0 in km
+ */
+ private T computeH0(final Field field, final int month, final T azr) {
+
+ // One
+ final T one = field.getOne();
+
+ // Auxiliary parameter ka (Eq. 99 and 100)
+ final T ka;
+ if (month > 3 && month < 10) {
+ // month = 4,5,6,7,8,9
+ ka = azr.multiply(0.014).add(hmF2.multiply(0.008)).negate().add(6.705);
+ } else {
+ // month = 1,2,3,10,11,12
+ final T ratio = hmF2.divide(b2Bot);
+ ka = ratio.multiply(ratio).multiply(0.097).add(nmF2.multiply(0.153)).add(-7.77);
+ }
+
+ // Auxiliary parameter kb (Eq. 101 and 102)
+ T kb = join(ka, one.multiply(2.0), one, ka.subtract(2.0));
+ kb = join(one.multiply(8.0), kb, one, kb.subtract(8.0));
+
+ // Auxiliary parameter Ha (Eq. 103)
+ final T hA = kb.multiply(b2Bot);
+
+ // Auxiliary parameters x and v (Eq. 104 and 105)
+ final T x = hA.subtract(150.0).multiply(0.01);
+ final T v = x.multiply(0.041163).subtract(0.183981).multiply(x).add(1.424472);
+
+ // Topside thickness parameter (Eq. 106)
+ final T h = hA.divide(v);
+ return h;
+ }
+
+ /**
+ * A clipped exponential function.
+ *
+ * This function, describe in section F.2.12.2 of the reference document, is
+ * recommanded for the computation of exponential values.
+ *
+ * @param power power for exponential function
+ * @return clipped exponential value
+ */
+ private T clipExp(final T power) {
+ final T zero = power.getField().getZero();
+ if (power.getReal() > 80.0) {
+ return zero.add(5.5406E34);
+ } else if (power.getReal() < -80) {
+ return zero.add(1.8049E-35);
+ } else {
+ return FastMath.exp(power);
+ }
+ }
+
+ /**
+ * This method provides a third order interpolation function
+ * as recommended in the reference document (Ref Eq. 128 to Eq. 138)
+ *
+ * @param z1 z1 coefficient
+ * @param z2 z2 coefficient
+ * @param z3 z3 coefficient
+ * @param z4 z4 coefficient
+ * @param x position
+ * @return a third order interpolation
+ */
+ private T interpolate(final T z1, final T z2,
+ final T z3, final T z4,
+ final T x) {
+
+ if (FastMath.abs(2.0 * x.getReal()) < 1e-10) {
+ return z2;
+ }
+
+ final T delta = x.multiply(2.0).subtract(1.0);
+ final T g1 = z3.add(z2);
+ final T g2 = z3.subtract(z2);
+ final T g3 = z4.add(z1);
+ final T g4 = z4.subtract(z1).divide(3.0);
+ final T a0 = g1.multiply(9.0).subtract(g3);
+ final T a1 = g2.multiply(9.0).subtract(g4);
+ final T a2 = g3.subtract(g1);
+ final T a3 = g4.subtract(g2);
+ final T zx = delta.multiply(a3).add(a2).multiply(delta).add(a1).multiply(delta).add(a0).multiply(0.0625);
+
+ return zx;
+ }
+
+ /**
+ * Allows smooth joining of functions f1 and f2
+ * (i.e. continuous first derivatives) at origin.
+ *
+ * This function, describe in section F.2.12.1 of the reference document, is
+ * recommanded for computational efficiency.
+ *
+ * @param dF1 first function
+ * @param dF2 second function
+ * @param dA width of transition region
+ * @param dX x value
+ * @return the computed value
+ */
+ private T join(final T dF1, final T dF2,
+ final T dA, final T dX) {
+ final T ee = clipExp(dA.multiply(dX));
+ return dF1.multiply(ee).add(dF2).divide(ee.add(1.0));
+ }
+
+ /**
+ * The Epstein function.
+ *
+ * This function, describe in section 2.5.1 of the reference document, is used
+ * as a basis analytical function in NeQuick for the construction of the ionospheric layers.
+ *
+ * @param x x parameter
+ * @param y y parameter
+ * @param z z parameter
+ * @param w w parameter
+ * @return value of the epstein function
+ */
+ private T epst(final T x, final T y,
+ final T z, final T w) {
+ final T ex = clipExp(w.subtract(y).divide(z));
+ final T opex = ex.add(1.0);
+ final T epst = x.multiply(ex).divide(opex.multiply(opex));
+ return epst;
+ }
+
+}
diff --git a/src/main/java/org/orekit/models/earth/ionosphere/GlobalIonosphereMapModel.java b/src/main/java/org/orekit/models/earth/ionosphere/GlobalIonosphereMapModel.java
index b04eaca231f82ae3b17d63893d8af9faa7bd0698..cd0c467543e21531273d46821c98961b2e1dea17 100644
--- a/src/main/java/org/orekit/models/earth/ionosphere/GlobalIonosphereMapModel.java
+++ b/src/main/java/org/orekit/models/earth/ionosphere/GlobalIonosphereMapModel.java
@@ -499,7 +499,7 @@ public class GlobalIonosphereMapModel implements IonosphericModel {
double[] longitudes = null;
AbsoluteDate firstEpoch = null;
AbsoluteDate lastEpoch = null;
- AbsoluteDate epoch = null;
+ AbsoluteDate epoch = firstEpoch;
ArrayList values = new ArrayList<>();
for (line = br.readLine(); line != null; line = br.readLine()) {
@@ -545,6 +545,14 @@ public class GlobalIonosphereMapModel implements IonosphericModel {
longitudes = parseCoordinate(line);
break;
case "END OF HEADER" :
+ // Check that latitude and longitude bondaries were found
+ if (latitudes == null || longitudes == null) {
+ throw new OrekitException(OrekitMessages.NO_LATITUDE_LONGITUDE_BONDARIES_IN_IONEX_HEADER, supportedNames);
+ }
+ // Check that first and last epochs were found
+ if (firstEpoch == null || lastEpoch == null) {
+ throw new OrekitException(OrekitMessages.NO_EPOCH_IN_IONEX_HEADER, supportedNames);
+ }
// At the end of the header, we build the IONEXHeader object
header = new IONEXHeader(firstEpoch, lastEpoch, interval, nbOfMaps,
baseRadius, hIon, mappingF);
diff --git a/src/main/java/org/orekit/models/earth/ionosphere/KlobucharIonoCoefficientsLoader.java b/src/main/java/org/orekit/models/earth/ionosphere/KlobucharIonoCoefficientsLoader.java
index 90e58259118fbcb42f5077101b4ecd7d424e587d..5a5f1275e5bdba6108cb024aaec0fd94823b58d0 100644
--- a/src/main/java/org/orekit/models/earth/ionosphere/KlobucharIonoCoefficientsLoader.java
+++ b/src/main/java/org/orekit/models/earth/ionosphere/KlobucharIonoCoefficientsLoader.java
@@ -21,6 +21,7 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import org.orekit.data.DataLoader;
@@ -153,7 +154,7 @@ public class KlobucharIonoCoefficientsLoader implements DataLoader {
throws IOException, ParseException {
// Open stream and parse data
- final BufferedReader br = new BufferedReader(new InputStreamReader(input, "UTF-8"));
+ final BufferedReader br = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
int lineNumber = 0;
final String splitter = "\\s+";
for (String line = br.readLine(); line != null; line = br.readLine()) {
diff --git a/src/main/java/org/orekit/models/earth/ionosphere/NeQuickModel.java b/src/main/java/org/orekit/models/earth/ionosphere/NeQuickModel.java
new file mode 100644
index 0000000000000000000000000000000000000000..8db8374b67d3d6dce45156b30c3234eea86ebcf5
--- /dev/null
+++ b/src/main/java/org/orekit/models/earth/ionosphere/NeQuickModel.java
@@ -0,0 +1,1552 @@
+/* Copyright 2002-2019 CS Systèmes d'Information
+ * Licensed to CS Systèmes d'Information (CS) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.models.earth.ionosphere;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.List;
+
+import org.hipparchus.Field;
+import org.hipparchus.RealFieldElement;
+import org.hipparchus.util.FastMath;
+import org.hipparchus.util.FieldSinCos;
+import org.hipparchus.util.MathArrays;
+import org.hipparchus.util.SinCos;
+import org.orekit.bodies.BodyShape;
+import org.orekit.bodies.FieldGeodeticPoint;
+import org.orekit.bodies.GeodeticPoint;
+import org.orekit.errors.OrekitException;
+import org.orekit.errors.OrekitMessages;
+import org.orekit.frames.TopocentricFrame;
+import org.orekit.propagation.FieldSpacecraftState;
+import org.orekit.propagation.SpacecraftState;
+import org.orekit.time.AbsoluteDate;
+import org.orekit.time.DateComponents;
+import org.orekit.time.DateTimeComponents;
+import org.orekit.time.FieldAbsoluteDate;
+import org.orekit.time.TimeScalesFactory;
+import org.orekit.utils.ParameterDriver;
+import org.orekit.utils.TimeStampedFieldPVCoordinates;
+import org.orekit.utils.TimeStampedPVCoordinates;
+
+/**
+ * NeQuick ionospheric delay model.
+ *
+ * @author Bryan Cazabonne
+ *
+ * @see "European Union (2016). European GNSS (Galileo) Open Service-Ionospheric Correction
+ * Algorithm for Galileo Single Frequency Users. 1.2."
+ *
+ * @since 10.1
+ */
+public class NeQuickModel implements IonosphericModel {
+
+ /** NeQuick resources base directory. */
+ private static final String NEQUICK_BASE = "/assets/org/orekit/nequick/";
+
+ /** Serializable UID. */
+ private static final long serialVersionUID = 201928051L;
+
+ /** Splitter for MODIP and CCIR files. */
+ private static final String SPLITER = "\\s+";
+
+ /** Mean Earth radius in m (Ref Table 2.5.2). */
+ private static final double RE = 6371200.0;
+
+ /** Meters to kilometers converter. */
+ private static final double M_TO_KM = 0.001;
+
+ /** Factor for the electron density computation. */
+ private static final double DENSITY_FACTOR = 1.0e11;
+
+ /** Factor for the path delay computation. */
+ private static final double DELAY_FACTOR = 40.3e16;
+
+ /** The three ionospheric coefficients broadcast in the Galileo navigation message. */
+ private final double[] alpha;
+
+ /** MODIP grid. */
+ private final double[][] stModip;
+
+ /** Month used for loading CCIR coefficients. */
+ private int month;
+
+ /** F2 coefficients used by the F2 layer. */
+ private double[][][] f2;
+
+ /** Fm3 coefficients used by the F2 layer. */
+ private double[][][] fm3;
+
+ /**
+ * Build a new instance.
+ * @param alpha effective ionisation level coefficients
+ */
+ public NeQuickModel(final double[] alpha) {
+ // F2 layer values
+ this.month = 0;
+ this.f2 = null;
+ this.fm3 = null;
+ // Read modip grid
+ final MODIPLoader parser = new MODIPLoader();
+ parser.loadMODIPGrid();
+ this.stModip = parser.getMODIPGrid();
+ // Ionisation level coefficients
+ this.alpha = alpha.clone();
+ }
+
+ @Override
+ public double pathDelay(final SpacecraftState state, final TopocentricFrame baseFrame,
+ final double frequency, final double[] parameters) {
+ // Point
+ final GeodeticPoint recPoint = baseFrame.getPoint();
+ // Date
+ final AbsoluteDate date = state.getDate();
+
+ // Reference body shape
+ final BodyShape ellipsoid = baseFrame.getParentShape();
+ // Satellite geodetic coordinates
+ final TimeStampedPVCoordinates pv = state.getPVCoordinates(ellipsoid.getBodyFrame());
+ final GeodeticPoint satPoint = ellipsoid.transform(pv.getPosition(), ellipsoid.getBodyFrame(), state.getDate());
+
+ // Total Electron Content
+ final double tec = stec(date, recPoint, satPoint);
+
+ // Ionospheric delay
+ final double factor = DELAY_FACTOR / (frequency * frequency);
+ return factor * tec;
+ }
+
+ @Override
+ public > T pathDelay(final FieldSpacecraftState state, final TopocentricFrame baseFrame,
+ final double frequency, final T[] parameters) {
+ // Date
+ final FieldAbsoluteDate date = state.getDate();
+ // Point
+ final FieldGeodeticPoint recPoint = baseFrame.getPoint(date.getField());
+
+
+ // Reference body shape
+ final BodyShape ellipsoid = baseFrame.getParentShape();
+ // Satellite geodetic coordinates
+ final TimeStampedFieldPVCoordinates pv = state.getPVCoordinates(ellipsoid.getBodyFrame());
+ final FieldGeodeticPoint satPoint = ellipsoid.transform(pv.getPosition(), ellipsoid.getBodyFrame(), state.getDate());
+
+ // Total Electron Content
+ final T tec = stec(date, recPoint, satPoint);
+
+ // Ionospheric delay
+ final double factor = DELAY_FACTOR / (frequency * frequency);
+ return tec.multiply(factor);
+ }
+
+ @Override
+ public List getParametersDrivers() {
+ return Collections.emptyList();
+ }
+
+ /**
+ * This method allows the computation of the Stant Total Electron Content (STEC).
+ *
+ * This method follows the Gauss algorithm exposed in section 2.5.8.2.8 of
+ * the reference document.
+ *
+ * @param date current date
+ * @param recP receiver position
+ * @param satP satellite position
+ * @return the STEC in TECUnits
+ */
+ public double stec(final AbsoluteDate date, final GeodeticPoint recP, final GeodeticPoint satP) {
+
+ // Ray-perigee parameters
+ final Ray ray = new Ray(recP, satP);
+
+ // Load the correct CCIR file
+ final DateTimeComponents dateTime = date.getComponents(TimeScalesFactory.getUTC());
+ loadsIfNeeded(dateTime.getDate());
+
+ // Tolerance for the integration accuracy. Defined inside the reference document, section 2.5.8.1.
+ final double h1 = recP.getAltitude();
+ final double tolerance;
+ if (h1 < 1000000.0) {
+ tolerance = 0.001;
+ } else {
+ tolerance = 0.01;
+ }
+
+ // Integration
+ int n = 8;
+ final Segment seg1 = new Segment(n, ray);
+ double gn1 = stecIntegration(seg1, dateTime);
+ n *= 2;
+ final Segment seg2 = new Segment(n, ray);
+ double gn2 = stecIntegration(seg2, dateTime);
+
+ int count = 1;
+ while (FastMath.abs(gn2 - gn1) > tolerance * FastMath.abs(gn1) && count < 20) {
+ gn1 = gn2;
+ n *= 2;
+ final Segment seg = new Segment(n, ray);
+ gn2 = stecIntegration(seg, dateTime);
+ count += 1;
+ }
+
+ // If count > 20 the integration did not converge
+ if (count == 20) {
+ throw new OrekitException(OrekitMessages.STEC_INTEGRATION_DID_NOT_CONVERGE);
+ }
+
+ // Eq. 202
+ return (gn2 + ((gn2 - gn1) / 15.0)) * 1.0e-16;
+ }
+
+ /**
+ * This method allows the computation of the Stant Total Electron Content (STEC).
+ *
+ * This method follows the Gauss algorithm exposed in section 2.5.8.2.8 of
+ * the reference document.
+ *
+ * @param type of the elements
+ * @param date current date
+ * @param recP receiver position
+ * @param satP satellite position
+ * @return the STEC in TECUnits
+ */
+ public > T stec(final FieldAbsoluteDate date,
+ final FieldGeodeticPoint recP,
+ final FieldGeodeticPoint satP) {
+
+ // Field
+ final Field field = date.getField();
+
+ // Ray-perigee parameters
+ final FieldRay ray = new FieldRay<>(field, recP, satP);
+
+ // Load the correct CCIR file
+ final DateTimeComponents dateTime = date.getComponents(TimeScalesFactory.getUTC());
+ loadsIfNeeded(dateTime.getDate());
+
+ // Tolerance for the integration accuracy. Defined inside the reference document, section 2.5.8.1.
+ final T h1 = recP.getAltitude();
+ final double tolerance;
+ if (h1.getReal() < 1000000.0) {
+ tolerance = 0.001;
+ } else {
+ tolerance = 0.01;
+ }
+
+ // Integration
+ int n = 8;
+ final FieldSegment seg1 = new FieldSegment<>(field, n, ray);
+ T gn1 = stecIntegration(field, seg1, dateTime);
+ n *= 2;
+ final FieldSegment seg2 = new FieldSegment<>(field, n, ray);
+ T gn2 = stecIntegration(field, seg2, dateTime);
+
+ int count = 1;
+ while (FastMath.abs(gn2.subtract(gn1)).getReal() > FastMath.abs(gn1).multiply(tolerance).getReal() && count < 20) {
+ gn1 = gn2;
+ n *= 2;
+ final FieldSegment seg = new FieldSegment<>(field, n, ray);
+ gn2 = stecIntegration(field, seg, dateTime);
+ count += 1;
+ }
+
+ // If count > 20 the integration did not converge
+ if (count == 20) {
+ throw new OrekitException(OrekitMessages.STEC_INTEGRATION_DID_NOT_CONVERGE);
+ }
+
+ // Eq. 202
+ return gn2.add(gn2.subtract(gn1).divide(15.0)).multiply(1.0e-16);
+ }
+
+ /**
+ * This method perfoms the STEC integration.
+ * @param seg coordinates along the integration path
+ * @param dateTime current date and time componentns
+ * @return result of the integration
+ */
+ private double stecIntegration(final Segment seg, final DateTimeComponents dateTime) {
+ // Integration points
+ final double[] heightS = seg.getHeights();
+ final double[] latitudeS = seg.getLatitudes();
+ final double[] longitudeS = seg.getLongitudes();
+
+ // Compute electron density
+ double density = 0.0;
+ for (int i = 0; i < heightS.length; i++) {
+ final NeQuickParameters parameters = new NeQuickParameters(dateTime, f2, fm3,
+ latitudeS[i], longitudeS[i],
+ alpha, stModip);
+ density += electronDensity(heightS[i], parameters);
+ }
+
+ return 0.5 * seg.getInterval() * density;
+ }
+
+ /**
+ * This method perfoms the STEC integration.
+ * @param type of the elements
+ * @param field field of the elements
+ * @param seg coordinates along the integration path
+ * @param dateTime current date and time componentns
+ * @return result of the integration
+ */
+ private > T stecIntegration(final Field field,
+ final FieldSegment seg,
+ final DateTimeComponents dateTime) {
+ // Integration points
+ final T[] heightS = seg.getHeights();
+ final T[] latitudeS = seg.getLatitudes();
+ final T[] longitudeS = seg.getLongitudes();
+
+ // Compute electron density
+ T density = field.getZero();
+ for (int i = 0; i < heightS.length; i++) {
+ final FieldNeQuickParameters parameters = new FieldNeQuickParameters<>(field, dateTime, f2, fm3,
+ latitudeS[i], longitudeS[i],
+ alpha, stModip);
+ density = density.add(electronDensity(field, heightS[i], parameters));
+ }
+
+ return seg.getInterval().multiply(density).multiply(0.5);
+ }
+
+ /**
+ * Computes the electron density at a given height.
+ * @param h height in m
+ * @param parameters NeQuick model parameters
+ * @return electron density [m^-3]
+ */
+ private double electronDensity(final double h, final NeQuickParameters parameters) {
+ // Convert height in kilometers
+ final double hInKm = h * M_TO_KM;
+ // Electron density
+ final double n;
+ if (hInKm <= parameters.getHmF2()) {
+ n = bottomElectronDensity(hInKm, parameters);
+ } else {
+ n = topElectronDensity(hInKm, parameters);
+ }
+ return n;
+ }
+
+ /**
+ * Computes the electron density at a given height.
+ * @param type of the elements
+ * @param field field of the elements
+ * @param h height in m
+ * @param parameters NeQuick model parameters
+ * @return electron density [m^-3]
+ */
+ private > T electronDensity(final Field field,
+ final T h,
+ final FieldNeQuickParameters parameters) {
+ // Convert height in kilometers
+ final T hInKm = h.multiply(M_TO_KM);
+ // Electron density
+ final T n;
+ if (hInKm.getReal() <= parameters.getHmF2().getReal()) {
+ n = bottomElectronDensity(field, hInKm, parameters);
+ } else {
+ n = topElectronDensity(field, hInKm, parameters);
+ }
+ return n;
+ }
+
+ /**
+ * Computes the electron density of the bottomside.
+ * @param h height in km
+ * @param parameters NeQuick model parameters
+ * @return the electron density N in m-3
+ */
+ private double bottomElectronDensity(final double h, final NeQuickParameters parameters) {
+
+ // Select the relevant B parameter for the current height (Eq. 109 and 110)
+ final double be;
+ if (h > parameters.getHmE()) {
+ be = parameters.getBETop();
+ } else {
+ be = parameters.getBEBot();
+ }
+ final double bf1;
+ if (h > parameters.getHmF1()) {
+ bf1 = parameters.getB1Top();
+ } else {
+ bf1 = parameters.getB1Bot();
+ }
+ final double bf2 = parameters.getB2Bot();
+
+ // Useful array of constants
+ final double[] ct = new double[] {
+ 1.0 / bf2, 1.0 / bf1, 1.0 / be
+ };
+
+ // Compute the exponential argument for each layer (Eq. 111 to 113)
+ // If h < 100km we use h = 100km as recommended in the reference document
+ final double hTemp = FastMath.max(100.0, h);
+ final double exp = clipExp(10.0 / (1.0 + FastMath.abs(hTemp - parameters.getHmF2())));
+ final double[] arguments = new double[3];
+ arguments[0] = (hTemp - parameters.getHmF2()) / bf2;
+ arguments[1] = ((hTemp - parameters.getHmF1()) / bf1) * exp;
+ arguments[2] = ((hTemp - parameters.getHmE()) / be) * exp;
+
+ // S coefficients
+ final double[] s = new double[3];
+ // Array of corrective terms
+ final double[] ds = new double[3];
+
+ // Layer amplitudes
+ final double[] amplitudes = parameters.getLayerAmplitudes();
+
+ // Fill arrays (Eq. 114 to 118)
+ for (int i = 0; i < 3; i++) {
+ if (FastMath.abs(arguments[i]) > 25.0) {
+ s[i] = 0.0;
+ ds[i] = 0.0;
+ } else {
+ final double expA = clipExp(arguments[i]);
+ final double opExpA = 1.0 + expA;
+ s[i] = amplitudes[i] * (expA / (opExpA * opExpA));
+ ds[i] = ct[i] * ((1.0 - expA) / (1.0 + expA));
+ }
+ }
+
+ // Electron density
+ final double aNo = MathArrays.linearCombination(s[0], 1.0, s[1], 1.0, s[2], 1.0);
+ if (h >= 100) {
+ return aNo * DENSITY_FACTOR;
+ } else {
+ // Chapman parameters (Eq. 119 and 120)
+ final double bc = 1.0 - 10.0 * (MathArrays.linearCombination(s[0], ds[0], s[1], ds[1], s[2], ds[2]) / aNo);
+ final double z = 0.1 * (h - 100.0);
+ // Electron density (Eq. 121)
+ return aNo * clipExp(1.0 - bc * z - clipExp(-z)) * DENSITY_FACTOR;
+ }
+ }
+
+ /**
+ * Computes the electron density of the bottomside.
+ * @param type of the elements
+ * @param field field of the elements
+ * @param h height in km
+ * @param parameters NeQuick model parameters
+ * @return the electron density N in m-3
+ */
+ private > T bottomElectronDensity(final Field field,
+ final T h,
+ final FieldNeQuickParameters parameters) {
+
+ // Zero and One
+ final T zero = field.getZero();
+ final T one = field.getOne();
+
+ // Select the relevant B parameter for the current height (Eq. 109 and 110)
+ final T be;
+ if (h.getReal() > parameters.getHmE().getReal()) {
+ be = parameters.getBETop();
+ } else {
+ be = parameters.getBEBot();
+ }
+ final T bf1;
+ if (h.getReal() > parameters.getHmF1().getReal()) {
+ bf1 = parameters.getB1Top();
+ } else {
+ bf1 = parameters.getB1Bot();
+ }
+ final T bf2 = parameters.getB2Bot();
+
+ // Useful array of constants
+ final T[] ct = MathArrays.buildArray(field, 3);
+ ct[0] = bf2.reciprocal();
+ ct[1] = bf1.reciprocal();
+ ct[2] = be.reciprocal();
+
+ // Compute the exponential argument for each layer (Eq. 111 to 113)
+ // If h < 100km we use h = 100km as recommended in the reference document
+ final T hTemp = FastMath.max(zero.add(100.0), h);
+ final T exp = clipExp(field, FastMath.abs(hTemp.subtract(parameters.getHmF2())).add(1.0).divide(10.0).reciprocal());
+ final T[] arguments = MathArrays.buildArray(field, 3);
+ arguments[0] = hTemp.subtract(parameters.getHmF2()).divide(bf2);
+ arguments[1] = hTemp.subtract(parameters.getHmF1()).divide(bf1).multiply(exp);
+ arguments[2] = hTemp.subtract(parameters.getHmE()).divide(be).multiply(exp);
+
+ // S coefficients
+ final T[] s = MathArrays.buildArray(field, 3);
+ // Array of corrective terms
+ final T[] ds = MathArrays.buildArray(field, 3);
+
+ // Layer amplitudes
+ final T[] amplitudes = parameters.getLayerAmplitudes();
+
+ // Fill arrays (Eq. 114 to 118)
+ for (int i = 0; i < 3; i++) {
+ if (FastMath.abs(arguments[i]).getReal() > 25.0) {
+ s[i] = zero;
+ ds[i] = zero;
+ } else {
+ final T expA = clipExp(field, arguments[i]);
+ final T opExpA = expA.add(1.0);
+ s[i] = amplitudes[i].multiply(expA.divide(opExpA.multiply(opExpA)));
+ ds[i] = ct[i].multiply(expA.negate().add(1.0).divide(expA.add(1.0)));
+ }
+ }
+
+ // Electron density
+ final T aNo = one.linearCombination(s[0], one, s[1], one, s[2], one);
+ if (h.getReal() >= 100) {
+ return aNo.multiply(DENSITY_FACTOR);
+ } else {
+ // Chapman parameters (Eq. 119 and 120)
+ final T bc = s[0].multiply(ds[0]).add(one.linearCombination(s[0], ds[0], s[1], ds[1], s[2], ds[2])).divide(aNo).multiply(10.0).negate().add(1.0);
+ final T z = h.subtract(100.0).multiply(0.1);
+ // Electron density (Eq. 121)
+ return aNo.multiply(clipExp(field, bc.multiply(z).add(clipExp(field, z.negate())).negate().add(1.0))).multiply(DENSITY_FACTOR);
+ }
+ }
+
+ /**
+ * Computes the electron density of the topside.
+ * @param h height in km
+ * @param parameters NeQuick model parameters
+ * @return the electron density N in m-3
+ */
+ private double topElectronDensity(final double h, final NeQuickParameters parameters) {
+
+ // Constant parameters (Eq. 122 and 123)
+ final double g = 0.125;
+ final double r = 100.0;
+
+ // Arguments deltaH and z (Eq. 124 and 125)
+ final double deltaH = h - parameters.getHmF2();
+ final double z = deltaH / (parameters.getH0() * (1.0 + (r * g * deltaH) / (r * parameters.getH0() + g * deltaH)));
+
+ // Exponential (Eq. 126)
+ final double ee = clipExp(z);
+
+ // Electron density (Eq. 127)
+ if (ee > 1.0e11) {
+ return (4.0 * parameters.getNmF2() / ee) * DENSITY_FACTOR;
+ } else {
+ final double opExpZ = 1.0 + ee;
+ return ((4.0 * parameters.getNmF2() * ee) / (opExpZ * opExpZ)) * DENSITY_FACTOR;
+ }
+ }
+
+ /**
+ * Computes the electron density of the topside.
+ * @param type of the elements
+ * @param field field of the elements
+ * @param h height in km
+ * @param parameters NeQuick model parameters
+ * @return the electron density N in m-3
+ */
+ private > T topElectronDensity(final Field field,
+ final T h,
+ final FieldNeQuickParameters parameters) {
+
+ // Constant parameters (Eq. 122 and 123)
+ final double g = 0.125;
+ final double r = 100.0;
+
+ // Arguments deltaH and z (Eq. 124 and 125)
+ final T deltaH = h.subtract(parameters.getHmF2());
+ final T z = deltaH.divide(parameters.getH0().multiply(deltaH.multiply(r).multiply(g).divide(parameters.getH0().multiply(r).add(deltaH.multiply(g))).add(1.0)));
+
+ // Exponential (Eq. 126)
+ final T ee = clipExp(field, z);
+
+ // Electron density (Eq. 127)
+ if (ee.getReal() > 1.0e11) {
+ return parameters.getNmF2().multiply(4.0).divide(ee).multiply(DENSITY_FACTOR);
+ } else {
+ final T opExpZ = ee.add(field.getOne());
+ return parameters.getNmF2().multiply(4.0).multiply(ee).divide(opExpZ.multiply(opExpZ)).multiply(DENSITY_FACTOR);
+ }
+ }
+
+ /**
+ * Lazy loading of CCIR data.
+ * @param date current date components
+ */
+ private void loadsIfNeeded(final DateComponents date) {
+
+ // Current month
+ final int currentMonth = date.getMonth();
+
+ // Check if date have changed or if f2 and fm3 arrays are null
+ if (currentMonth != month || f2 == null || fm3 == null) {
+ this.month = currentMonth;
+
+ // Read file
+ final CCIRLoader loader = new CCIRLoader();
+ loader.loadCCIRCoefficients(date);
+
+ // Update arrays
+ this.f2 = loader.getF2();
+ this.fm3 = loader.getFm3();
+ }
+ }
+
+ /**
+ * A clipped exponential function.
+ *
+ * This function, describe in section F.2.12.2 of the reference document, is
+ * recommanded for the computation of exponential values.
+ *
+ * @param power power for exponential function
+ * @return clipped exponential value
+ */
+ private double clipExp(final double power) {
+ if (power > 80.0) {
+ return 5.5406E34;
+ } else if (power < -80) {
+ return 1.8049E-35;
+ } else {
+ return FastMath.exp(power);
+ }
+ }
+
+ /**
+ * A clipped exponential function.
+ *
+ * This function, describe in section F.2.12.2 of the reference document, is
+ * recommanded for the computation of exponential values.
+ *
+ * @param type of the elements
+ * @param field field of the elements
+ * @param power power for exponential function
+ * @return clipped exponential value
+ */
+ private > T clipExp(final Field field, final T power) {
+ final T zero = field.getZero();
+ if (power.getReal() > 80.0) {
+ return zero.add(5.5406E34);
+ } else if (power.getReal() < -80) {
+ return zero.add(1.8049E-35);
+ } else {
+ return FastMath.exp(power);
+ }
+ }
+
+ /** Get a data stream.
+ * @param name file name of the resource stream
+ * @return stream
+ */
+ private static InputStream getStream(final String name) {
+ return NeQuickModel.class.getResourceAsStream(name);
+ }
+
+ /**
+ * Parser for Modified Dip Latitude (MODIP) grid file.
+ *
+ * The MODIP grid allows to estimate MODIP μ [deg] at a given point (φ,λ)
+ * by interpolation of the relevant values contained in the support file.
+ *
+ * The file contains the values of MODIP (expressed in degrees) on a geocentric grid
+ * from 90°S to 90°N with a 5-degree step in latitude and from 180°W to 180°E with a
+ * 10-degree in longitude.
+ *
+ */
+ private static class MODIPLoader {
+
+ /** Supported name for MODIP grid. */
+ private static final String SUPPORTED_NAME = NEQUICK_BASE + "modip.txt";
+
+ /** MODIP grid. */
+ private double[][] grid;
+
+ /**
+ * Build a new instance.
+ */
+ MODIPLoader() {
+ this.grid = null;
+ }
+
+ /** Returns the MODIP grid array.
+ * @return the MODIP grid array
+ */
+ public double[][] getMODIPGrid() {
+ return grid.clone();
+ }
+
+ /**
+ * Load the data using supported names.
+ */
+ public void loadMODIPGrid() {
+ try (InputStream in = getStream(SUPPORTED_NAME)) {
+ loadData(in, SUPPORTED_NAME);
+ } catch (IOException e) {
+ throw new OrekitException(OrekitMessages.INTERNAL_ERROR, e);
+ }
+
+ // Throw an exception if MODIP grid was not loaded properly
+ if (grid == null) {
+ throw new OrekitException(OrekitMessages.MODIP_GRID_NOT_LOADED, SUPPORTED_NAME);
+ }
+ }
+
+ /**
+ * Load data from a stream.
+ * @param input input stream
+ * @param name name of the file
+ * @throws IOException if data can't be read
+ */
+ public void loadData(final InputStream input, final String name)
+ throws IOException {
+
+ // Grid size
+ final int size = 39;
+
+ // Initialize array
+ final double[][] array = new double[size][size];
+
+ // Open stream and parse data
+ int lineNumber = 0;
+ String line = null;
+ try (InputStreamReader isr = new InputStreamReader(input, StandardCharsets.UTF_8);
+ BufferedReader br = new BufferedReader(isr)) {
+
+ for (line = br.readLine(); line != null; line = br.readLine()) {
+ ++lineNumber;
+ line = line.trim();
+
+ // Read grid data
+ if (line.length() > 0) {
+ final String[] modip_line = line.split(SPLITER);
+ for (int column = 0; column < modip_line.length; column++) {
+ array[lineNumber - 1][column] = Double.valueOf(modip_line[column]);
+ }
+ }
+
+ }
+
+ } catch (NumberFormatException nfe) {
+ throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
+ lineNumber, name, line);
+ }
+
+ // Close the stream after reading
+ input.close();
+
+ // Clone parsed grid
+ grid = array.clone();
+
+ }
+ }
+
+ /**
+ * Parser for CCIR files.
+ *
+ * Numerical grid maps which describe the regular variation of the ionosphere.
+ * They are used to derive other variables such as critical frequencies and transmission factors.
+ *
+ * The coefficients correspond to low and high solar activity conditions.
+ *
+ * The CCIR file naming convention is ccirXX.asc where each XX means month + 10.
+ *
+ * Coefficients are store into tow arrays, F2 and Fm3. F2 coefficients are used for the computation
+ * of the F2 layer critical frequency. Fm3 for the computation of the F2 layer maximum usable frequency factor.
+ * The size of these two arrays is fixed and discussed into the section 2.5.3.2
+ * of the reference document.
+ *
+ */
+ private static class CCIRLoader {
+
+ /** Default supported files name pattern. */
+ public static final String DEFAULT_SUPPORTED_NAME = "ccir**.asc";
+
+ /** Total number of F2 coefficients contained in the file. */
+ private static final int NUMBER_F2_COEFFICIENTS = 1976;
+
+ /** Rows number for F2 and Fm3 arrays. */
+ private static final int ROWS = 2;
+
+ /** Columns number for F2 array. */
+ private static final int TOTAL_COLUMNS_F2 = 76;
+
+ /** Columns number for Fm3 array. */
+ private static final int TOTAL_COLUMNS_FM3 = 49;
+
+ /** Depth of F2 array. */
+ private static final int DEPTH_F2 = 13;
+
+ /** Depth of Fm3 array. */
+ private static final int DEPTH_FM3 = 9;
+
+ /** Regular expression for supported file name. */
+ private String supportedName;
+
+ /** F2 coefficients used for the computation of the F2 layer critical frequency. */
+ private double[][][] f2Loader;
+
+ /** Fm3 coefficients used for the computation of the F2 layer maximum usable frequency factor. */
+ private double[][][] fm3Loader;
+
+ /**
+ * Build a new instance.
+ */
+ CCIRLoader() {
+ this.supportedName = DEFAULT_SUPPORTED_NAME;
+ this.f2Loader = null;
+ this.fm3Loader = null;
+ }
+
+ /**
+ * Get the F2 coefficients used for the computation of the F2 layer critical frequency.
+ * @return the F2 coefficients
+ */
+ public double[][][] getF2() {
+ return f2Loader.clone();
+ }
+
+ /**
+ * Get the Fm3 coefficients used for the computation of the F2 layer maximum usable frequency factor.
+ * @return the F2 coefficients
+ */
+ public double[][][] getFm3() {
+ return fm3Loader.clone();
+ }
+
+ /** Load the data for a given month.
+ * @param dateComponents month given but its DateComponents
+ */
+ public void loadCCIRCoefficients(final DateComponents dateComponents) {
+
+ // The files are named ccirXX.asc where XX substitute the month of the year + 10
+ final int currentMonth = dateComponents.getMonth();
+ this.supportedName = NEQUICK_BASE + String.format("ccir%02d.asc", currentMonth + 10);
+ try (InputStream in = getStream(supportedName)) {
+ loadData(in, supportedName);
+ } catch (IOException e) {
+ throw new OrekitException(OrekitMessages.INTERNAL_ERROR, e);
+ }
+ // Throw an exception if F2 or Fm3 were not loaded properly
+ if (f2Loader == null || fm3Loader == null) {
+ throw new OrekitException(OrekitMessages.NEQUICK_F2_FM3_NOT_LOADED, supportedName);
+ }
+
+ }
+
+ /**
+ * Load data from a stream.
+ * @param input input stream
+ * @param name name of the file
+ * @throws IOException if data can't be read
+ */
+ public void loadData(final InputStream input, final String name)
+ throws IOException {
+
+ // Initialize arrays
+ final double[][][] f2Temp = new double[ROWS][TOTAL_COLUMNS_F2][DEPTH_F2];
+ final double[][][] fm3Temp = new double[ROWS][TOTAL_COLUMNS_FM3][DEPTH_FM3];
+
+ // Placeholders for parsed data
+ int lineNumber = 0;
+ int index = 0;
+ int currentRowF2 = 0;
+ int currentColumnF2 = 0;
+ int currentDepthF2 = 0;
+ int currentRowFm3 = 0;
+ int currentColumnFm3 = 0;
+ int currentDepthFm3 = 0;
+ String line = null;
+
+ try (InputStreamReader isr = new InputStreamReader(input, StandardCharsets.UTF_8);
+ BufferedReader br = new BufferedReader(isr)) {
+
+ for (line = br.readLine(); line != null; line = br.readLine()) {
+ ++lineNumber;
+ line = line.trim();
+
+ // Read grid data
+ if (line.length() > 0) {
+ final String[] ccir_line = line.split(SPLITER);
+ for (int i = 0; i < ccir_line.length; i++) {
+
+ if (index < NUMBER_F2_COEFFICIENTS) {
+ // Parse F2 coefficients
+ if (currentDepthF2 >= DEPTH_F2 && currentColumnF2 < (TOTAL_COLUMNS_F2 - 1)) {
+ currentDepthF2 = 0;
+ currentColumnF2++;
+ } else if (currentDepthF2 >= DEPTH_F2 && currentColumnF2 >= (TOTAL_COLUMNS_F2 - 1)) {
+ currentDepthF2 = 0;
+ currentColumnF2 = 0;
+ currentRowF2++;
+ }
+ f2Temp[currentRowF2][currentColumnF2][currentDepthF2++] = Double.valueOf(ccir_line[i]);
+ index++;
+ } else {
+ // Parse Fm3 coefficients
+ if (currentDepthFm3 >= DEPTH_FM3 && currentColumnFm3 < (TOTAL_COLUMNS_FM3 - 1)) {
+ currentDepthFm3 = 0;
+ currentColumnFm3++;
+ } else if (currentDepthFm3 >= DEPTH_FM3 && currentColumnFm3 >= (TOTAL_COLUMNS_FM3 - 1)) {
+ currentDepthFm3 = 0;
+ currentColumnFm3 = 0;
+ currentRowFm3++;
+ }
+ fm3Temp[currentRowFm3][currentColumnFm3][currentDepthFm3++] = Double.valueOf(ccir_line[i]);
+ index++;
+ }
+
+ }
+ }
+
+ }
+
+ } catch (NumberFormatException nfe) {
+ throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
+ lineNumber, name, line);
+ }
+
+ // Close the stream after reading
+ input.close();
+
+ f2Loader = f2Temp.clone();
+ fm3Loader = fm3Temp.clone();
+
+ }
+
+ }
+
+ /**
+ * Container for ray-perigee parameters.
+ * By convention, point 1 is at lower height.
+ */
+ private static class Ray {
+
+ /** Threshold for ray-perigee parameters computation. */
+ private static final double THRESHOLD = 1.0e-10;
+
+ /** Distance of the first point from the ray perigee [m]. */
+ private final double s1;
+
+ /** Distance of the second point from the ray perigee [m]. */
+ private final double s2;
+
+ /** Ray-perigee radius [m]. */
+ private final double rp;
+
+ /** Ray-perigee latitude [rad]. */
+ private final double latP;
+
+ /** Ray-perigee longitude [rad]. */
+ private final double lonP;
+
+ /** Sine of azimuth of satellite as seen from ray-perigee. */
+ private final double sinAzP;
+
+ /** Cosine of azimuth of satellite as seen from ray-perigee. */
+ private final double cosAzP;
+
+ /**
+ * Constructor.
+ * @param recP receiver position
+ * @param satP satellite position
+ */
+ Ray(final GeodeticPoint recP, final GeodeticPoint satP) {
+
+ // Integration limits in meters (Eq. 140 and 141)
+ final double r1 = RE + recP.getAltitude();
+ final double r2 = RE + satP.getAltitude();
+
+ // Useful parameters
+ final double lat1 = recP.getLatitude();
+ final double lat2 = satP.getLatitude();
+ final double lon1 = recP.getLongitude();
+ final double lon2 = satP.getLongitude();
+ final SinCos scLatSat = FastMath.sinCos(lat2);
+ final SinCos scLatRec = FastMath.sinCos(lat1);
+
+ // Zenith angle computation (Eq. 153 to 155)
+ final double cosD = scLatRec.sin() * scLatSat.sin() +
+ scLatRec.cos() * scLatSat.cos() * FastMath.cos(lon2 - lon1);
+ final double sinD = FastMath.sqrt(1.0 - cosD * cosD);
+ final double z = FastMath.atan2(sinD, cosD - (r1 / r2));
+
+ // Ray-perigee computation in meters (Eq. 156)
+ this.rp = r1 * FastMath.sin(z);
+
+ // Ray-perigee latitude and longitude
+ if (FastMath.abs(FastMath.abs(lat1) - 0.5 * FastMath.PI) < THRESHOLD) {
+
+ // Ray-perigee latitude (Eq. 157)
+ if (lat1 < 0) {
+ this.latP = -z;
+ } else {
+ this.latP = z;
+ }
+
+ // Ray-perigee longitude (Eq. 164)
+ if (z < 0) {
+ this.lonP = lon2;
+ } else {
+ this.lonP = lon2 + FastMath.PI;
+ }
+
+ } else {
+
+ // Ray-perigee latitude (Eq. 158 to 163)
+ final double deltaP = 0.5 * FastMath.PI - z;
+ final SinCos scDeltaP = FastMath.sinCos(deltaP);
+ final double sinAz = FastMath.sin(lon2 - lon1) * scLatSat.cos() / sinD;
+ final double cosAz = (scLatSat.sin() - cosD * scLatRec.sin()) / (sinD * scLatRec.cos());
+ final double sinLatP = scLatRec.sin() * scDeltaP.cos() - scLatRec.cos() * scDeltaP.sin() * cosAz;
+ final double cosLatP = FastMath.sqrt(1.0 - sinLatP * sinLatP);
+ this.latP = FastMath.atan2(sinLatP, cosLatP);
+
+ // Ray-perigee longitude (Eq. 165 to 167)
+ final double sinLonP = -sinAz * scDeltaP.sin() / cosLatP;
+ final double cosLonP = (scDeltaP.cos() - scLatRec.sin() * sinLatP) / (scLatRec.cos() * cosLatP);
+ this.lonP = FastMath.atan2(sinLonP, cosLonP) + lon1;
+
+ }
+
+ // Sine and cosie of azimuth of satellite as seen from ray-perigee
+ final double psi = greatCircleAngle(lat2, lon2);
+ if (FastMath.abs(FastMath.abs(latP) - 0.5 * FastMath.PI) < THRESHOLD) {
+ // Eq. 172 and 173
+ this.sinAzP = 0.0;
+ if (latP < 0.0) {
+ this.cosAzP = 1;
+ } else {
+ this.cosAzP = -1;
+ }
+ } else {
+ // Eq. 174 and 175
+ this.sinAzP = scLatSat.cos() * FastMath.sin(lon2 - lonP) / FastMath.sin(psi);
+ this.cosAzP = (scLatSat.sin() - FastMath.sin(latP) * FastMath.cos(psi)) / (FastMath.cos(latP) * FastMath.sin(psi));
+ }
+
+ // Integration en points s1 and s2 in meters (Eq. 176 and 177)
+ this.s1 = FastMath.sqrt(r1 * r1 - rp * rp);
+ this.s2 = FastMath.sqrt(r2 * r2 - rp * rp);
+ }
+
+ /**
+ * Get the distance of the first point from the ray perigee.
+ * @return s1 in meters
+ */
+ public double getS1() {
+ return s1;
+ }
+
+ /**
+ * Get the distance of the second point from the ray perigee.
+ * @return s2 in meters
+ */
+ public double getS2() {
+ return s2;
+ }
+
+ /**
+ * Get the ray-perigee radius.
+ * @return the ray-perigee radius in meters
+ */
+ public double getRadius() {
+ return rp;
+ }
+
+ /**
+ * Get the ray-perigee latitude.
+ * @return the ray-perigee latitude in radians
+ */
+ public double getLatitude() {
+ return latP;
+ }
+
+ /**
+ * Get the ray-perigee longitude.
+ * @return the ray-perigee longitude in radians
+ */
+ public double getLongitude() {
+ return lonP;
+ }
+
+ /**
+ * Get the sine of azimuth of satellite as seen from ray-perigee.
+ * @return the sine of azimuth
+ */
+ public double getSineAz() {
+ return sinAzP;
+ }
+
+ /**
+ * Get the cosine of azimuth of satellite as seen from ray-perigee.
+ * @return the cosine of azimuth
+ */
+ public double getCosineAz() {
+ return cosAzP;
+ }
+
+ /**
+ * Compute the great circle angle from ray-perigee to satellite.
+ *
+ * This method used the equations 168 to 171 pf the reference document.
+ *
+ * @param latitude satellite latitude in radians
+ * @param longitude satellite longitude in radians
+ * @return the great circle angle in radians
+ */
+ private double greatCircleAngle(final double latitude, final double longitude) {
+ if (FastMath.abs(FastMath.abs(latP) - 0.5 * FastMath.PI) < THRESHOLD) {
+ return FastMath.abs(latitude - latP);
+ } else {
+ final double cosPhi = FastMath.sin(latP) * FastMath.sin(latitude) +
+ FastMath.cos(latP) * FastMath.cos(latitude) * FastMath.cos(longitude - lonP);
+ final double sinPhi = FastMath.sqrt(1.0 - cosPhi * cosPhi);
+ return FastMath.atan2(sinPhi, cosPhi);
+ }
+ }
+ }
+
+ /**
+ * Container for ray-perigee parameters.
+ * By convention, point 1 is at lower height.
+ */
+ private static class FieldRay > {
+
+ /** Threshold for ray-perigee parameters computation. */
+ private static final double THRESHOLD = 1.0e-10;
+
+ /** Distance of the first point from the ray perigee [m]. */
+ private final T s1;
+
+ /** Distance of the second point from the ray perigee [m]. */
+ private final T s2;
+
+ /** Ray-perigee radius [m]. */
+ private final T rp;
+
+ /** Ray-perigee latitude [rad]. */
+ private final T latP;
+
+ /** Ray-perigee longitude [rad]. */
+ private final T lonP;
+
+ /** Sine of azimuth of satellite as seen from ray-perigee. */
+ private final T sinAzP;
+
+ /** Cosine of azimuth of satellite as seen from ray-perigee. */
+ private final T cosAzP;
+
+ /**
+ * Constructor.
+ * @param field field of the elements
+ * @param recP receiver position
+ * @param satP satellite position
+ */
+ FieldRay(final Field field, final FieldGeodeticPoint recP, final FieldGeodeticPoint satP) {
+
+ // Integration limits in meters (Eq. 140 and 141)
+ final T r1 = recP.getAltitude().add(RE);
+ final T r2 = satP.getAltitude().add(RE);
+
+ // Useful parameters
+ final T lat1 = recP.getLatitude();
+ final T lat2 = satP.getLatitude();
+ final T lon1 = recP.getLongitude();
+ final T lon2 = satP.getLongitude();
+ final FieldSinCos scLatSat = FastMath.sinCos(lat2);
+ final FieldSinCos scLatRec = FastMath.sinCos(lat1);
+
+ // Zenith angle computation (Eq. 153 to 155)
+ final T cosD = scLatRec.sin().multiply(scLatSat.sin()).
+ add(scLatRec.cos().multiply(scLatSat.cos()).multiply(FastMath.cos(lon2.subtract(lon1))));
+ final T sinD = FastMath.sqrt(cosD.multiply(cosD).negate().add(1.0));
+ final T z = FastMath.atan2(sinD, cosD.subtract(r1.divide(r2)));
+
+ // Ray-perigee computation in meters (Eq. 156)
+ this.rp = r1.multiply(FastMath.sin(z));
+
+ // Ray-perigee latitude and longitude
+ if (FastMath.abs(FastMath.abs(lat1).getReal() - 0.5 * FastMath.PI) < THRESHOLD) {
+
+ // Ray-perigee latitude (Eq. 157)
+ if (lat1.getReal() < 0) {
+ this.latP = z.negate();
+ } else {
+ this.latP = z;
+ }
+
+ // Ray-perigee longitude (Eq. 164)
+ if (z.getReal() < 0) {
+ this.lonP = lon2;
+ } else {
+ this.lonP = lon2.add(FastMath.PI);
+ }
+
+ } else {
+
+ // Ray-perigee latitude (Eq. 158 to 163)
+ final T deltaP = z.negate().add(0.5 * FastMath.PI);
+ final FieldSinCos scDeltaP = FastMath.sinCos(deltaP);
+ final T sinAz = FastMath.sin(lon2.subtract(lon1)).multiply(scLatSat.cos()).divide(sinD);
+ final T cosAz = scLatSat.sin().subtract(cosD.multiply(scLatRec.sin())).divide(sinD.multiply(scLatRec.cos()));
+ final T sinLatP = scLatRec.sin().multiply(scDeltaP.cos()).subtract(scLatRec.cos().multiply(scDeltaP.sin()).multiply(cosAz));
+ final T cosLatP = FastMath.sqrt(sinLatP.multiply(sinLatP).negate().add(1.0));
+ this.latP = FastMath.atan2(sinLatP, cosLatP);
+
+ // Ray-perigee longitude (Eq. 165 to 167)
+ final T sinLonP = sinAz.negate().multiply(scDeltaP.sin()).divide(cosLatP);
+ final T cosLonP = scDeltaP.cos().subtract(scLatRec.sin().multiply(sinLatP)).divide(scLatRec.cos().multiply(cosLatP));
+ this.lonP = FastMath.atan2(sinLonP, cosLonP).add(lon1);
+
+ }
+
+ // Sine and cosie of azimuth of satellite as seen from ray-perigee
+ final T psi = greatCircleAngle(lat2, lon2);
+ if (FastMath.abs(FastMath.abs(latP).getReal() - 0.5 * FastMath.PI) < THRESHOLD) {
+ // Eq. 172 and 173
+ this.sinAzP = field.getZero();
+ if (latP.getReal() < 0.0) {
+ this.cosAzP = field.getOne();
+ } else {
+ this.cosAzP = field.getOne().negate();
+ }
+ } else {
+ // Eq. 174 and 175
+ this.sinAzP = scLatSat.cos().multiply(FastMath.sin(lon2.subtract(lonP))).divide(FastMath.sin(psi));
+ this.cosAzP = scLatSat.sin().subtract(FastMath.sin(latP).multiply(FastMath.cos(psi))).divide(FastMath.cos(latP).multiply(FastMath.sin(psi)));
+ }
+
+ // Integration en points s1 and s2 in meters (Eq. 176 and 177)
+ this.s1 = FastMath.sqrt(r1.multiply(r1).subtract(rp.multiply(rp)));
+ this.s2 = FastMath.sqrt(r2.multiply(r2).subtract(rp.multiply(rp)));
+ }
+
+ /**
+ * Get the distance of the first point from the ray perigee.
+ * @return s1 in meters
+ */
+ public T getS1() {
+ return s1;
+ }
+
+ /**
+ * Get the distance of the second point from the ray perigee.
+ * @return s2 in meters
+ */
+ public T getS2() {
+ return s2;
+ }
+
+ /**
+ * Get the ray-perigee radius.
+ * @return the ray-perigee radius in meters
+ */
+ public T getRadius() {
+ return rp;
+ }
+
+ /**
+ * Get the ray-perigee latitude.
+ * @return the ray-perigee latitude in radians
+ */
+ public T getLatitude() {
+ return latP;
+ }
+
+ /**
+ * Get the ray-perigee longitude.
+ * @return the ray-perigee longitude in radians
+ */
+ public T getLongitude() {
+ return lonP;
+ }
+
+ /**
+ * Get the sine of azimuth of satellite as seen from ray-perigee.
+ * @return the sine of azimuth
+ */
+ public T getSineAz() {
+ return sinAzP;
+ }
+
+ /**
+ * Get the cosine of azimuth of satellite as seen from ray-perigee.
+ * @return the cosine of azimuth
+ */
+ public T getCosineAz() {
+ return cosAzP;
+ }
+
+ /**
+ * Compute the great circle angle from ray-perigee to satellite.
+ *
+ * This method used the equations 168 to 171 pf the reference document.
+ *
+ * @param latitude satellite latitude in radians
+ * @param longitude satellite longitude in radians
+ * @return the great circle angle in radians
+ */
+ private T greatCircleAngle(final T latitude, final T longitude) {
+ if (FastMath.abs(FastMath.abs(latP).getReal() - 0.5 * FastMath.PI) < THRESHOLD) {
+ return FastMath.abs(latitude.subtract(latP));
+ } else {
+ final T cosPhi = FastMath.sin(latP).multiply(FastMath.sin(latitude)).
+ add(FastMath.cos(latP).multiply(FastMath.cos(latitude)).multiply(FastMath.cos(longitude.subtract(lonP))));
+ final T sinPhi = FastMath.sqrt(cosPhi.multiply(cosPhi).negate().add(1.0));
+ return FastMath.atan2(sinPhi, cosPhi);
+ }
+ }
+ }
+
+ /** Performs the computation of the coordinates along the integration path. */
+ private static class Segment {
+
+ /** Latitudes [rad]. */
+ private final double[] latitudes;
+
+ /** Longitudes [rad]. */
+ private final double[] longitudes;
+
+ /** Heights [m]. */
+ private final double[] heights;
+
+ /** Integration step [m]. */
+ private final double deltaN;
+
+ /**
+ * Constructor.
+ * @param n number of points used for the integration
+ * @param ray ray-perigee parameters
+ */
+ Segment(final int n, final Ray ray) {
+ // Integration en points
+ final double s1 = ray.getS1();
+ final double s2 = ray.getS2();
+
+ // Integration step (Eq. 195)
+ this.deltaN = (s2 - s1) / n;
+
+ // Segments
+ final double[] s = getSegments(n, s1, s2);
+
+ // Useful parameters
+ final double rp = ray.getRadius();
+ final SinCos scLatP = FastMath.sinCos(ray.getLatitude());
+
+ // Geodetic coordinates
+ final int size = s.length;
+ heights = new double[size];
+ latitudes = new double[size];
+ longitudes = new double[size];
+ for (int i = 0; i < size; i++) {
+ // Heights (Eq. 178)
+ heights[i] = FastMath.sqrt(s[i] * s[i] + rp * rp) - RE;
+
+ // Great circle parameters (Eq. 179 to 181)
+ final double tanDs = s[i] / rp;
+ final double cosDs = 1.0 / FastMath.sqrt(1.0 + tanDs * tanDs);
+ final double sinDs = tanDs * cosDs;
+
+ // Latitude (Eq. 182 to 183)
+ final double sinLatS = scLatP.sin() * cosDs + scLatP.cos() * sinDs * ray.getCosineAz();
+ final double cosLatS = FastMath.sqrt(1.0 - sinLatS * sinLatS);
+ latitudes[i] = FastMath.atan2(sinLatS, cosLatS);
+
+ // Longitude (Eq. 184 to 187)
+ final double sinLonS = sinDs * ray.getSineAz() * scLatP.cos();
+ final double cosLonS = cosDs - scLatP.sin() * sinLatS;
+ longitudes[i] = FastMath.atan2(sinLonS, cosLonS) + ray.getLongitude();
+ }
+ }
+
+ /**
+ * Computes the distance of a point from the ray-perigee.
+ * @param n number of points used for the integration
+ * @param s1 lower boundary
+ * @param s2 upper boundary
+ * @return the distance of a point from the ray-perigee in km
+ */
+ private double[] getSegments(final int n, final double s1, final double s2) {
+ // Eq. 196
+ final double g = 0.5773502691896 * deltaN;
+ // Eq. 197
+ final double y = s1 + (deltaN - g) * 0.5;
+ final double[] segments = new double[2 * n];
+ int index = 0;
+ for (int i = 0; i < n; i++) {
+ // Eq. 198
+ segments[index] = y + i * deltaN;
+ index++;
+ segments[index] = y + i * deltaN + g;
+ index++;
+ }
+ return segments;
+ }
+
+ /**
+ * Get the latitudes of the coordinates along the integration path.
+ * @return the latitudes in radians
+ */
+ public double[] getLatitudes() {
+ return latitudes;
+ }
+
+ /**
+ * Get the longitudes of the coordinates along the integration path.
+ * @return the longitudes in radians
+ */
+ public double[] getLongitudes() {
+ return longitudes;
+ }
+
+ /**
+ * Get the heights of the coordinates along the integration path.
+ * @return the heights in m
+ */
+ public double[] getHeights() {
+ return heights;
+ }
+
+ /**
+ * Get the integration step.
+ * @return the integration step in meters
+ */
+ public double getInterval() {
+ return deltaN;
+ }
+ }
+
+ /** Performs the computation of the coordinates along the integration path. */
+ private static class FieldSegment > {
+
+ /** Latitudes [rad]. */
+ private final T[] latitudes;
+
+ /** Longitudes [rad]. */
+ private final T[] longitudes;
+
+ /** Heights [m]. */
+ private final T[] heights;
+
+ /** Integration step [m]. */
+ private final T deltaN;
+
+ /**
+ * Constructor.
+ * @param field field of the elements
+ * @param n number of points used for the integration
+ * @param ray ray-perigee parameters
+ */
+ FieldSegment(final Field field, final int n, final FieldRay ray) {
+ // Integration en points
+ final T s1 = ray.getS1();
+ final T s2 = ray.getS2();
+
+ // Integration step (Eq. 195)
+ this.deltaN = s2.subtract(s1).divide(n);
+
+ // Segments
+ final T[] s = getSegments(field, n, s1, s2);
+
+ // Useful parameters
+ final T rp = ray.getRadius();
+ final FieldSinCos scLatP = FastMath.sinCos(ray.getLatitude());
+
+ // Geodetic coordinates
+ final int size = s.length;
+ heights = MathArrays.buildArray(field, size);
+ latitudes = MathArrays.buildArray(field, size);
+ longitudes = MathArrays.buildArray(field, size);
+ for (int i = 0; i < size; i++) {
+ // Heights (Eq. 178)
+ heights[i] = FastMath.sqrt(s[i].multiply(s[i]).add(rp.multiply(rp))).subtract(RE);
+
+ // Great circle parameters (Eq. 179 to 181)
+ final T tanDs = s[i].divide(rp);
+ final T cosDs = FastMath.sqrt(tanDs.multiply(tanDs).add(1.0)).reciprocal();
+ final T sinDs = tanDs.multiply(cosDs);
+
+ // Latitude (Eq. 182 to 183)
+ final T sinLatS = scLatP.sin().multiply(cosDs).add(scLatP.cos().multiply(sinDs).multiply(ray.getCosineAz()));
+ final T cosLatS = FastMath.sqrt(sinLatS.multiply(sinLatS).negate().add(1.0));
+ latitudes[i] = FastMath.atan2(sinLatS, cosLatS);
+
+ // Longitude (Eq. 184 to 187)
+ final T sinLonS = sinDs.multiply(ray.getSineAz()).multiply(scLatP.cos());
+ final T cosLonS = cosDs.subtract(scLatP.sin().multiply(sinLatS));
+ longitudes[i] = FastMath.atan2(sinLonS, cosLonS).add(ray.getLongitude());
+ }
+ }
+
+ /**
+ * Computes the distance of a point from the ray-perigee.
+ * @param field field of the elements
+ * @param n number of points used for the integration
+ * @param s1 lower boundary
+ * @param s2 upper boundary
+ * @return the distance of a point from the ray-perigee in km
+ */
+ private T[] getSegments(final Field field, final int n, final T s1, final T s2) {
+ // Eq. 196
+ final T g = deltaN.multiply(0.5773502691896);
+ // Eq. 197
+ final T y = s1.add(deltaN.subtract(g).multiply(0.5));
+ final T[] segments = MathArrays.buildArray(field, 2 * n);
+ int index = 0;
+ for (int i = 0; i < n; i++) {
+ // Eq. 198
+ segments[index] = y.add(deltaN.multiply(i));
+ index++;
+ segments[index] = y.add(deltaN.multiply(i)).add(g);
+ index++;
+ }
+ return segments;
+ }
+
+ /**
+ * Get the latitudes of the coordinates along the integration path.
+ * @return the latitudes in radians
+ */
+ public T[] getLatitudes() {
+ return latitudes;
+ }
+
+ /**
+ * Get the longitudes of the coordinates along the integration path.
+ * @return the longitudes in radians
+ */
+ public T[] getLongitudes() {
+ return longitudes;
+ }
+
+ /**
+ * Get the heights of the coordinates along the integration path.
+ * @return the heights in m
+ */
+ public T[] getHeights() {
+ return heights;
+ }
+
+ /**
+ * Get the integration step.
+ * @return the integration step in meters
+ */
+ public T getInterval() {
+ return deltaN;
+ }
+ }
+
+}
diff --git a/src/main/java/org/orekit/models/earth/ionosphere/NeQuickParameters.java b/src/main/java/org/orekit/models/earth/ionosphere/NeQuickParameters.java
new file mode 100644
index 0000000000000000000000000000000000000000..679751c3a6881906c02b4cfefc8dbe09d1d57123
--- /dev/null
+++ b/src/main/java/org/orekit/models/earth/ionosphere/NeQuickParameters.java
@@ -0,0 +1,768 @@
+/* Copyright 2002-2019 CS Systèmes d'Information
+ * Licensed to CS Systèmes d'Information (CS) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.models.earth.ionosphere;
+
+import org.hipparchus.util.FastMath;
+import org.hipparchus.util.MathArrays;
+import org.hipparchus.util.SinCos;
+import org.orekit.time.DateComponents;
+import org.orekit.time.DateTimeComponents;
+import org.orekit.time.TimeComponents;
+
+/**
+ * This class perfoms the computation of the parameters used by the NeQuick model.
+ *
+ * @author Bryan Cazabonne
+ *
+ * @see "European Union (2016). European GNSS (Galileo) Open Service-Ionospheric Correction
+ * Algorithm for Galileo Single Frequency Users. 1.2."
+ *
+ * @since 10.1
+ */
+class NeQuickParameters {
+
+ /** Solar zenith angle at day night transition, degrees. */
+ private static final double X0 = 86.23292796211615;
+
+ /** F2 layer maximum density. */
+ private final double nmF2;
+
+ /** F2 layer maximum density height [km]. */
+ private final double hmF2;
+
+ /** F1 layer maximum density height [km]. */
+ private final double hmF1;
+
+ /** E layer maximum density height [km]. */
+ private final double hmE;
+
+ /** F2 layer bottom thickness parameter [km]. */
+ private final double b2Bot;
+
+ /** F1 layer top thickness parameter [km]. */
+ private final double b1Top;
+
+ /** F1 layer bottom thickness parameter [km]. */
+ private final double b1Bot;
+
+ /** E layer top thickness parameter [km]. */
+ private final double beTop;
+
+ /** E layer bottom thickness parameter [km]. */
+ private final double beBot;
+
+ /** topside thickness parameter [km]. */
+ private final double h0;
+
+ /** Layer amplitudes. */
+ private final double[] amplitudes;
+
+ /**
+ * Build a new instance.
+ * @param dateTime current date time components
+ * @param f2 F2 coefficients used by the F2 layer
+ * @param fm3 Fm3 coefficients used by the F2 layer
+ * @param latitude latitude of a point along the integration path, in radians
+ * @param longitude longitude of a point along the integration path, in radians
+ * @param alpha effective ionisation level coefficients
+ * @param modipGrip modip grid
+ */
+ NeQuickParameters(final DateTimeComponents dateTime, final double[][][] f2,
+ final double[][][] fm3, final double latitude, final double longitude,
+ final double[] alpha, final double[][] modipGrip) {
+
+ // MODIP in degrees
+ final double modip = computeMODIP(latitude, longitude, modipGrip);
+ // Effective ionisation level Az
+ final double az = computeAz(modip, alpha);
+ // Effective sunspot number (Eq. 19)
+ final double azr = FastMath.sqrt(167273.0 + (az - 63.7) * 1123.6) - 408.99;
+ // Date and Time components
+ final DateComponents date = dateTime.getDate();
+ final TimeComponents time = dateTime.getTime();
+ // Hours
+ final double hours = time.getSecondsInUTCDay() / 3600.0;
+ // Effective solar zenith angle in radians
+ final double xeff = computeEffectiveSolarAngle(date.getMonth(), hours, latitude, longitude);
+
+ // Coefficients for F2 layer parameters
+ // Compute the array of interpolated coefficients for foF2 (Eq. 44)
+ final double[][] af2 = new double[76][13];
+ for (int j = 0; j < 76; j++) {
+ for (int k = 0; k < 13; k++ ) {
+ af2[j][k] = f2[0][j][k] * (1.0 - (azr * 0.01)) + f2[1][j][k] * (azr * 0.01);
+ }
+ }
+
+ // Compute the array of interpolated coefficients for M(3000)F2 (Eq. 46)
+ final double[][] am3 = new double[49][9];
+ for (int j = 0; j < 49; j++) {
+ for (int k = 0; k < 9; k++ ) {
+ am3[j][k] = fm3[0][j][k] * (1.0 - (azr * 0.01)) + fm3[1][j][k] * (azr * 0.01);
+ }
+ }
+
+ // E layer maximum density height in km (Eq. 78)
+ this.hmE = 120.0;
+ // E layer critical frequency in MHz
+ final double foE = computefoE(date.getMonth(), az, xeff, latitude);
+ // E layer maximum density in 10^11 m-3 (Eq. 36)
+ final double nmE = 0.124 * foE * foE;
+
+ // Time argument (Eq. 49)
+ final double t = FastMath.toRadians(15 * hours) - FastMath.PI;
+ // Compute Fourier time series for foF2 and M(3000)F2
+ final double[] cf2 = computeCF2(af2, t);
+ final double[] cm3 = computeCm3(am3, t);
+ // F2 layer critical frequency in MHz
+ final double foF2 = computefoF2(modip, cf2, latitude, longitude);
+ // Maximum Usable Frequency factor
+ final double mF2 = computeMF2(modip, cm3, latitude, longitude);
+ // F2 layer maximum density in 10^11 m-3
+ this.nmF2 = 0.124 * foF2 * foF2;
+ // F2 layer maximum density height in km
+ this.hmF2 = computehmF2(foE, foF2, mF2);
+
+ // F1 layer critical frequency in MHz
+ final double foF1 = computefoF1(foE, foF2);
+ // F1 layer maximum density in 10^11 m-3
+ final double nmF1;
+ if (foF1 <= 0.0 && foE > 2.0) {
+ final double foEpopf = foE + 0.5;
+ nmF1 = 0.124 * foEpopf * foEpopf;
+ } else {
+ nmF1 = 0.124 * foF1 * foF1;
+ }
+ // F1 layer maximum density height in km
+ this.hmF1 = 0.5 * (hmF2 + hmE);
+
+ // Thickness parameters (Eq. 85 to 89)
+ final double a = 0.01 * clipExp(-3.467 + 0.857 * FastMath.log(foF2 * foF2) + 2.02 * FastMath.log(mF2));
+ this.b2Bot = 0.385 * nmF2 / a;
+ this.b1Top = 0.3 * (hmF2 - hmF1);
+ this.b1Bot = 0.5 * (hmF1 - hmE);
+ this.beTop = FastMath.max(b1Bot, 7.0);
+ this.beBot = 5.0;
+
+ // Layer amplitude coefficients
+ this.amplitudes = computeLayerAmplitudes(nmE, nmF1, foF1);
+
+ // Topside thickness parameter
+ this.h0 = computeH0(date.getMonth(), azr);
+ }
+
+ /**
+ * Get the F2 layer maximum density.
+ * @return nmF2
+ */
+ public double getNmF2() {
+ return nmF2;
+ }
+
+ /**
+ * Get the F2 layer maximum density height.
+ * @return hmF2 in km
+ */
+ public double getHmF2() {
+ return hmF2;
+ }
+
+ /**
+ * Get the F1 layer maximum density height.
+ * @return hmF1 in km
+ */
+ public double getHmF1() {
+ return hmF1;
+ }
+
+ /**
+ * Get the E layer maximum density height.
+ * @return hmE in km
+ */
+ public double getHmE() {
+ return hmE;
+ }
+
+ /**
+ * Get the F2 layer thickness parameter (bottom).
+ * @return B2Bot in km
+ */
+ public double getB2Bot() {
+ return b2Bot;
+ }
+
+ /**
+ * Get the F1 layer thickness parameter (top).
+ * @return B1Top in km
+ */
+ public double getB1Top() {
+ return b1Top;
+ }
+
+ /**
+ * Get the F1 layer thickness parameter (bottom).
+ * @return B1Bot in km
+ */
+ public double getB1Bot() {
+ return b1Bot;
+ }
+
+ /**
+ * Get the E layer thickness parameter (bottom).
+ * @return BeBot in km
+ */
+ public double getBEBot() {
+ return beBot;
+ }
+
+ /**
+ * Get the E layer thickness parameter (top).
+ * @return BeTop in km
+ */
+ public double getBETop() {
+ return beTop;
+ }
+
+ /**
+ * Get the F2, F1 and E layer amplitudes.
+ *
+ * The resulting element is an array having the following form:
+ *
+ * - double[0] = A1 → F2 layer amplitude
+ *
- double[1] = A2 → F1 layer amplitude
+ *
- double[2] = A3 → E layer amplitude
+ *
+ * @return layer amplitudes
+ */
+ public double[] getLayerAmplitudes() {
+ return amplitudes.clone();
+ }
+
+ /**
+ * Get the topside thickness parameter H0.
+ * @return H0 in km
+ */
+ public double getH0() {
+ return h0;
+ }
+
+ /**
+ * Computes the value of the modified dip latitude (MODIP) for the
+ * given latitude and longitude.
+ *
+ * @param lat receiver latitude, radians
+ * @param lon receiver longitude, radians
+ * @param stModip modip grid
+ * @return the MODIP in degrees
+ */
+ private double computeMODIP(final double lat, final double lon, final double[][] stModip) {
+
+ // For the MODIP computation, latitude and longitude have to be converted in degrees
+ final double latitude = FastMath.toDegrees(lat);
+ final double longitude = FastMath.toDegrees(lon);
+
+ // Extreme cases
+ if (latitude == 90.0 || latitude == -90.0) {
+ return latitude;
+ }
+
+ // Auxiliary parameter l (Eq. 6 to 8)
+ final int lF = (int) ((longitude + 180) * 0.1);
+ int l = lF - 2;
+ if (l < 0) {
+ l += 36;
+ } else if (l > 33) {
+ l -= 36;
+ }
+
+ // Auxiliary parameter a (Eq. 9 to 11)
+ final double a = 0.2 * (latitude + 90) + 1.0;
+ final double aF = FastMath.floor(a);
+ // Eq. 10
+ final double x = a - aF;
+ // Eq. 11
+ final int i = (int) aF - 2;
+
+ // zi coefficients (Eq. 12 and 13)
+ final double z1 = interpolate(stModip[i + 1][l + 2], stModip[i + 2][l + 2], stModip[i + 3][l + 2], stModip[i + 4][l + 2], x);
+ final double z2 = interpolate(stModip[i + 1][l + 3], stModip[i + 2][l + 3], stModip[i + 3][l + 3], stModip[i + 4][l + 3], x);
+ final double z3 = interpolate(stModip[i + 1][l + 4], stModip[i + 2][l + 4], stModip[i + 3][l + 4], stModip[i + 4][l + 4], x);
+ final double z4 = interpolate(stModip[i + 1][l + 5], stModip[i + 2][l + 5], stModip[i + 3][l + 5], stModip[i + 4][l + 5], x);
+
+ // Auxiliary parameter b (Eq. 14 and 15)
+ final double b = (longitude + 180) * 0.1;
+ final double bF = FastMath.floor(b);
+ final double y = b - bF;
+
+ // MODIP (Ref Eq. 16)
+ final double modip = interpolate(z1, z2, z3, z4, y);
+
+ return modip;
+ }
+
+ /**
+ * This method computes the effective ionisation level Az.
+ *
+ * This parameter is used for the computation of the Total Electron Content (TEC).
+ *
+ * @param modip modified dip latitude (MODIP) in degrees
+ * @param alpha effective ionisation level coefficients
+ * @return the ionisation level Az
+ */
+ private double computeAz(final double modip, final double[] alpha) {
+ // Particular condition (Eq. 17)
+ if (alpha[0] == 0.0 && alpha[1] == 0.0 && alpha[2] == 0.0) {
+ return 63.7;
+ }
+ // Az = a0 + modip * a1 + modip^2 * a2 (Eq. 18)
+ double az = alpha[0] + modip * (alpha[1] + modip * alpha[2]);
+ // If Az < 0 -> Az = 0
+ az = FastMath.max(0.0, az);
+ // If Az > 400 -> Az = 400
+ az = FastMath.min(400.0, az);
+ return az;
+ }
+
+ /**
+ * This method computes the effective solar zenith angle.
+ *
+ * The effective solar zenith angle is compute as a function of the
+ * solar zenith angle and the solar zenith angle at day night transition.
+ *
+ * @param month current month of the year
+ * @param hours universal time (hours)
+ * @param latitude in radians
+ * @param longitude in radians
+ * @return the effective solar zenith angle, radians
+ */
+ private double computeEffectiveSolarAngle(final int month,
+ final double hours,
+ final double latitude,
+ final double longitude) {
+ // Local time (Eq.4)
+ final double lt = hours + longitude / FastMath.toRadians(15.0);
+ // Day of year at the middle of the month (Eq. 20)
+ final double dy = 30.5 * month - 15.0;
+ // Time (Eq. 21)
+ final double t = dy + (18 - hours) / 24;
+ // Arguments am and al (Eq. 22 and 23)
+ final double am = FastMath.toRadians(0.9856 * t - 3.289);
+ final double al = am + FastMath.toRadians(1.916 * FastMath.sin(am) + 0.020 * FastMath.sin(2.0 * am) + 282.634);
+ // Sine and cosine of solar declination (Eq. 24 and 25)
+ final double sDec = 0.39782 * FastMath.sin(al);
+ final double cDec = FastMath.sqrt(1. - sDec * sDec);
+ // Solar zenith angle, deg (Eq. 26 and 27)
+ final SinCos scLat = FastMath.sinCos(latitude);
+ final double coef = (FastMath.PI / 12) * (12 - lt);
+ final double cZenith = scLat.sin() * sDec + scLat.cos() * cDec * FastMath.cos(coef);
+ final double angle = FastMath.atan2(FastMath.sqrt(1.0 - cZenith * cZenith), cZenith);
+ final double x = FastMath.toDegrees(angle);
+ // Effective solar zenith angle (Eq. 28)
+ final double xeff = join(90.0 - 0.24 * clipExp(20.0 - 0.2 * x), x, 12.0, x - X0);
+ return FastMath.toRadians(xeff);
+ }
+
+ /**
+ * This method computes the E layer critical frequency at a given location.
+ * @param month current month
+ * @param az ffective ionisation level
+ * @param xeff effective solar zenith angle in radians
+ * @param latitude latitude in radians
+ * @return the E layer critical frequency at a given location in MHz
+ */
+ private double computefoE(final int month, final double az,
+ final double xeff, final double latitude) {
+ // The latitude has to be converted in degrees
+ final double lat = FastMath.toDegrees(latitude);
+ // Square root of the effective ionisation level
+ final double sqAz = FastMath.sqrt(az);
+ // seas parameter (Eq. 30 to 32)
+ final int seas;
+ if (month == 1 || month == 2 || month == 11 || month == 12) {
+ seas = -1;
+ } else if (month == 3 || month == 4 || month == 9 || month == 10) {
+ seas = 0;
+ } else {
+ seas = 1;
+ }
+ // Latitudinal dependence (Eq. 33 and 34)
+ final double ee = clipExp(0.3 * lat);
+ final double seasp = seas * ((ee - 1.0) / (ee + 1.0));
+ // Critical frequency (Eq. 35)
+ final double coef = 1.112 - 0.019 * seasp;
+ final double foE = FastMath.sqrt(coef * coef * sqAz * FastMath.pow(FastMath.cos(xeff), 0.6) + 0.49);
+ return foE;
+ }
+
+ /**
+ * Computes the F2 layer height of maximum electron density.
+ * @param foE E layer layer critical frequency in MHz
+ * @param foF2 F2 layer layer critical frequency in MHz
+ * @param mF2 maximum usable frequency factor
+ * @return hmF2 in km
+ */
+ private double computehmF2(final double foE, final double foF2, final double mF2) {
+ // Ratio
+ final double fo = foF2 / foE;
+ final double ratio = join(fo, 1.75, 20.0, fo - 1.75);
+
+ // deltaM parameter
+ double deltaM = -0.012;
+ if (foE >= 1e-30) {
+ deltaM += 0.253 / (ratio - 1.215);
+ }
+
+ // hmF2 Eq. 80
+ final double mF2Sq = mF2 * mF2;
+ final double temp = FastMath.sqrt((0.0196 * mF2Sq + 1) / (1.2967 * mF2Sq - 1.0));
+ final double height = ((1490.0 * mF2 * temp) / (mF2 + deltaM)) - 176.0;
+ return height;
+ }
+
+ /**
+ * Computes cf2 coefficients.
+ * @param af2 interpolated coefficients for foF2
+ * @param t time argument
+ * @return the cf2 coefficients array
+ */
+ private double[] computeCF2(final double[][] af2, final double t) {
+ // Eq. 50
+ final double[] cf2 = new double[76];
+ for (int i = 0; i < cf2.length; i++) {
+ double sum = 0.0;
+ for (int k = 0; k < 6; k++) {
+ sum += af2[i][2 * k + 1] * FastMath.sin((k + 1) * t) + af2[i][2 * (k + 1)] * FastMath.cos((k + 1) * t);
+ }
+ cf2[i] = af2[i][0] + sum;
+ }
+ return cf2;
+ }
+
+ /**
+ * Computes Cm3 coefficients.
+ * @param am3 interpolated coefficients for foF2
+ * @param t time argument
+ * @return the Cm3 coefficients array
+ */
+ private double[] computeCm3(final double[][] am3, final double t) {
+ // Eq. 51
+ final double[] cm3 = new double[49];
+ for (int i = 0; i < cm3.length; i++) {
+ double sum = 0.0;
+ for (int k = 0; k < 4; k++) {
+ sum += am3[i][2 * k + 1] * FastMath.sin((k + 1) * t) + am3[i][2 * (k + 1)] * FastMath.cos((k + 1) * t);
+ }
+ cm3[i] = am3[i][0] + sum;
+ }
+ return cm3;
+ }
+
+ /**
+ * This method computes the F2 layer critical frequency.
+ * @param modip modified DIP latitude, in degrees
+ * @param cf2 Fourier time series for foF2
+ * @param latitude latitude in radians
+ * @param longitude longitude in radians
+ * @return the F2 layer critical frequency, MHz
+ */
+ private double computefoF2(final double modip, final double[] cf2,
+ final double latitude, final double longitude) {
+
+ // Legendre grades (Eq. 63)
+ final int[] q = new int[] {
+ 12, 12, 9, 5, 2, 1, 1, 1, 1
+ };
+
+ // Array for geographic terms
+ final double[] g = new double[cf2.length];
+ g[0] = 1.0;
+
+ // MODIP coefficients Eq. 57
+ final double sinMODIP = FastMath.sin(FastMath.toRadians(modip));
+ final double[] m = new double[12];
+ m[0] = 1.0;
+ for (int i = 1; i < q[0]; i++) {
+ m[i] = sinMODIP * m[i - 1];
+ g[i] = m[i];
+ }
+
+ // Latitude coefficients (Eq. 58)
+ final double cosLat = FastMath.cos(latitude);
+ final double[] p = new double[8];
+ p[0] = cosLat;
+ for (int n = 2; n < 9; n++) {
+ p[n - 1] = cosLat * p[n - 2];
+ }
+
+ // latitude and longitude terms
+ int index = 12;
+ for (int i = 1; i < q.length; i++) {
+ for (int j = 0; j < q[i]; j++) {
+ g[index++] = m[j] * p[i - 1] * FastMath.cos(i * longitude);
+ g[index++] = m[j] * p[i - 1] * FastMath.sin(i * longitude);
+ }
+ }
+
+ // Compute foF2 by linear combination
+ final double frequency = MathArrays.linearCombination(cf2, g);
+ return frequency;
+ }
+
+ /**
+ * This method computes the Maximum Usable Frequency factor.
+ * @param modip modified DIP latitude, in degrees
+ * @param cm3 Fourier time series for M(3000)F2
+ * @param latitude latitude in radians
+ * @param longitude longitude in radians
+ * @return the Maximum Usable Frequency factor
+ */
+ private double computeMF2(final double modip, final double[] cm3,
+ final double latitude, final double longitude) {
+
+ // Legendre grades (Eq. 71)
+ final int[] r = new int[] {
+ 7, 8, 6, 3, 2, 1, 1
+ };
+
+ // Array for geographic terms
+ final double[] g = new double[cm3.length];
+ g[0] = 1.0;
+
+ // MODIP coefficients Eq. 57
+ final double sinMODIP = FastMath.sin(FastMath.toRadians(modip));
+ final double[] m = new double[12];
+ m[0] = 1.0;
+ for (int i = 1; i < 12; i++) {
+ m[i] = sinMODIP * m[i - 1];
+ if (i < 7) {
+ g[i] = m[i];
+ }
+ }
+
+ // Latitude coefficients (Eq. 58)
+ final double cosLat = FastMath.cos(latitude);
+ final double[] p = new double[8];
+ p[0] = cosLat;
+ for (int n = 2; n < 9; n++) {
+ p[n - 1] = cosLat * p[n - 2];
+ }
+
+ // latitude and longitude terms
+ int index = 7;
+ for (int i = 1; i < r.length; i++) {
+ for (int j = 0; j < r[i]; j++) {
+ g[index++] = m[j] * p[i - 1] * FastMath.cos(i * longitude);
+ g[index++] = m[j] * p[i - 1] * FastMath.sin(i * longitude);
+ }
+ }
+
+ // Compute m3000 by linear combination
+ final double m3000 = MathArrays.linearCombination(g, cm3);
+ return m3000;
+ }
+
+ /**
+ * This method computes the F1 layer critical frequency.
+ *
+ * This computation performs the algorithm exposed in Annex F
+ * of the reference document.
+ *
+ * @param foE the E layer critical frequency, MHz
+ * @return the F1 layer critical frequency, MHz
+ * @param foF2 the F2 layer critical frequency, MHz
+ */
+ private double computefoF1(final double foE, final double foF2) {
+ final double temp = join(1.4 * foE, 0.0, 1000.0, foE - 2.0);
+ final double temp2 = join(0.0, temp, 1000.0, foE - temp);
+ final double value = join(temp2, 0.85 * temp2, 60.0, 0.85 * foF2 - temp2);
+ if (value < 1.0E-6) {
+ return 0.0;
+ } else {
+ return value;
+ }
+ }
+
+ /**
+ * This method allows the computation of the F2, F1 and E layer amplitudes.
+ *
+ * The resulting element is an array having the following form:
+ *
+ * - double[0] = A1 → F2 layer amplitude
+ *
- double[1] = A2 → F1 layer amplitude
+ *
- double[2] = A3 → E layer amplitude
+ *
+ *
+ * @param nmE E layer maximum density in 10^11 m-3
+ * @param nmF1 F1 layer maximum density in 10^11 m-3
+ * @param foF1 F1 layer critical frequency in MHz
+ * @return a three components array containing the layer amplitudes
+ */
+ private double[] computeLayerAmplitudes(final double nmE, final double nmF1, final double foF1) {
+ // Initialize array
+ final double[] amplitude = new double[3];
+
+ // F2 layer amplitude (Eq. 90)
+ final double a1 = 4.0 * nmF2;
+ amplitude[0] = a1;
+
+ // F1 and E layer amplitudes (Eq. 91 to 98)
+ if (foF1 < 0.5) {
+ amplitude[1] = 0.0;
+ amplitude[2] = 4.0 * (nmE - epst(a1, hmF2, b2Bot, hmE));
+ } else {
+ double a2a = 0.0;
+ double a3a = 4.0 * nmE;
+ for (int i = 0; i < 5; i++) {
+ a2a = 4.0 * (nmF1 - epst(a1, hmF2, b2Bot, hmF1) - epst(a3a, hmE, beTop, hmF1));
+ a2a = join(a2a, 0.8 * nmF1, 1.0, a2a - 0.8 * nmF1);
+ a3a = 4.0 * (nmE - epst(a2a, hmF1, b1Bot, hmE) - epst(a1, hmF2, b2Bot, hmE));
+ }
+ amplitude[1] = a2a;
+ amplitude[2] = join(a3a, 0.05, 60.0, a3a - 0.005);
+ }
+
+ return amplitude;
+ }
+
+ /**
+ * This method computes the topside thickness parameter H0.
+ *
+ * @param month current month
+ * @param azr effective sunspot number
+ * @return H0 in km
+ */
+ private double computeH0(final int month, final double azr) {
+
+ // Auxiliary parameter ka (Eq. 99 and 100)
+ final double ka;
+ if (month > 3 && month < 10) {
+ // month = 4,5,6,7,8,9
+ ka = 6.705 - 0.014 * azr - 0.008 * hmF2;
+ } else {
+ // month = 1,2,3,10,11,12
+ final double ratio = hmF2 / b2Bot;
+ ka = -7.77 + 0.097 * ratio * ratio + 0.153 * nmF2;
+ }
+
+ // Auxiliary parameter kb (Eq. 101 and 102)
+ double kb = join(ka, 2.0, 1.0, ka - 2.0);
+ kb = join(8.0, kb, 1.0, kb - 8.0);
+
+ // Auxiliary parameter Ha (Eq. 103)
+ final double hA = kb * b2Bot;
+
+ // Auxiliary parameters x and v (Eq. 104 and 105)
+ final double x = 0.01 * (hA - 150.0);
+ final double v = (0.041163 * x - 0.183981) * x + 1.424472;
+
+ // Topside thickness parameter (Eq. 106)
+ final double h = hA / v;
+ return h;
+ }
+
+ /**
+ * A clipped exponential function.
+ *
+ * This function, describe in section F.2.12.2 of the reference document, is
+ * recommanded for the computation of exponential values.
+ *
+ * @param power power for exponential function
+ * @return clipped exponential value
+ */
+ private double clipExp(final double power) {
+ if (power > 80.0) {
+ return 5.5406E34;
+ } else if (power < -80) {
+ return 1.8049E-35;
+ } else {
+ return FastMath.exp(power);
+ }
+ }
+
+ /**
+ * This method provides a third order interpolation function
+ * as recommended in the reference document (Ref Eq. 128 to Eq. 138)
+ *
+ * @param z1 z1 coefficient
+ * @param z2 z2 coefficient
+ * @param z3 z3 coefficient
+ * @param z4 z4 coefficient
+ * @param x position
+ * @return a third order interpolation
+ */
+ private double interpolate(final double z1, final double z2,
+ final double z3, final double z4,
+ final double x) {
+
+ if (FastMath.abs(2.0 * x) < 1e-10) {
+ return z2;
+ }
+
+ final double delta = 2.0 * x - 1.0;
+ final double g1 = z3 + z2;
+ final double g2 = z3 - z2;
+ final double g3 = z4 + z1;
+ final double g4 = (z4 - z1) / 3.0;
+ final double a0 = 9.0 * g1 - g3;
+ final double a1 = 9.0 * g2 - g4;
+ final double a2 = g3 - g1;
+ final double a3 = g4 - g2;
+ final double zx = 0.0625 * (a0 + delta * (a1 + delta * (a2 + delta * a3)));
+
+ return zx;
+ }
+
+ /**
+ * Allows smooth joining of functions f1 and f2
+ * (i.e. continuous first derivatives) at origin.
+ *
+ * This function, describe in section F.2.12.1 of the reference document, is
+ * recommanded for computational efficiency.
+ *
+ * @param dF1 first function
+ * @param dF2 second function
+ * @param dA width of transition region
+ * @param dX x value
+ * @return the computed value
+ */
+ private double join(final double dF1, final double dF2,
+ final double dA, final double dX) {
+ final double ee = clipExp(dA * dX);
+ return (dF1 * ee + dF2) / (ee + 1.0);
+ }
+
+ /**
+ * The Epstein function.
+ *
+ * This function, describe in section 2.5.1 of the reference document, is used
+ * as a basis analytical function in NeQuick for the construction of the ionospheric layers.
+ *
+ * @param x x parameter
+ * @param y y parameter
+ * @param z z parameter
+ * @param w w parameter
+ * @return value of the epstein function
+ */
+ private double epst(final double x, final double y,
+ final double z, final double w) {
+ final double ex = clipExp((w - y) / z);
+ final double opex = 1.0 + ex;
+ final double epst = x * ex / (opex * opex);
+ return epst;
+ }
+
+}
diff --git a/src/main/java/org/orekit/models/earth/troposphere/SaastamoinenModel.java b/src/main/java/org/orekit/models/earth/troposphere/SaastamoinenModel.java
index eb16e1cace675b09ed625fe87d9436c9972aedfd..f4fec4efaa4020e3a20c15ea272d45d471a36971 100644
--- a/src/main/java/org/orekit/models/earth/troposphere/SaastamoinenModel.java
+++ b/src/main/java/org/orekit/models/earth/troposphere/SaastamoinenModel.java
@@ -151,9 +151,9 @@ public class SaastamoinenModel implements DiscreteTroposphericModel {
public double pathDelay(final double elevation, final double height,
final double[] parameters, final AbsoluteDate date) {
- // there are no data in the model for negative altitudes
- // we use the data for the lowest available altitude: 0.0
- final double fixedHeight = FastMath.max(0.0, height);
+ // there are no data in the model for negative altitudes and altitude bigger than 5000 m
+ // limit the height to a range of [0, 5000] m
+ final double fixedHeight = FastMath.min(FastMath.max(0, height), 5000);
// the corrected temperature using a temperature gradient of -6.5 K/km
final double T = t0 - 6.5e-3 * fixedHeight;
@@ -192,9 +192,9 @@ public class SaastamoinenModel implements DiscreteTroposphericModel {
final Field field = height.getField();
final T zero = field.getZero();
- // there are no data in the model for negative altitudes
- // we use the data for the lowest available altitude: 0.0
- final T fixedHeight = FastMath.max(zero, height);
+ // there are no data in the model for negative altitudes and altitude bigger than 5000 m
+ // limit the height to a range of [0, 5000] m
+ final T fixedHeight = FastMath.min(FastMath.max(zero, height), zero.add(5000));
// the corrected temperature using a temperature gradient of -6.5 K/km
final T T = fixedHeight.multiply(6.5e-3).negate().add(t0);
diff --git a/src/main/java/org/orekit/models/earth/troposphere/ViennaModelCoefficientsLoader.java b/src/main/java/org/orekit/models/earth/troposphere/ViennaModelCoefficientsLoader.java
index daa3cf6aa85d243f164085f79777b43b632d5eee..5074cdfd530da799acef11f47649ad02688ce3a8 100644
--- a/src/main/java/org/orekit/models/earth/troposphere/ViennaModelCoefficientsLoader.java
+++ b/src/main/java/org/orekit/models/earth/troposphere/ViennaModelCoefficientsLoader.java
@@ -20,6 +20,7 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.util.ArrayList;
@@ -238,7 +239,7 @@ public class ViennaModelCoefficientsLoader implements DataLoader {
throws IOException, ParseException {
// Open stream and parse data
- final BufferedReader br = new BufferedReader(new InputStreamReader(input, "UTF-8"));
+ final BufferedReader br = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
int lineNumber = 0;
final String splitter = "\\s+";
diff --git a/src/main/java/org/orekit/orbits/FieldCircularOrbit.java b/src/main/java/org/orekit/orbits/FieldCircularOrbit.java
index 5e3118ec3f41197c191d2e329c3e3f43ce944e21..d09a6838d29f4b901c481d04a868d3a11bfd00df 100644
--- a/src/main/java/org/orekit/orbits/FieldCircularOrbit.java
+++ b/src/main/java/org/orekit/orbits/FieldCircularOrbit.java
@@ -30,6 +30,7 @@ import org.hipparchus.analysis.interpolation.FieldHermiteInterpolator;
import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
import org.hipparchus.util.FastMath;
import org.hipparchus.util.MathArrays;
+import org.hipparchus.util.MathUtils;
import org.orekit.errors.OrekitIllegalArgumentException;
import org.orekit.errors.OrekitInternalError;
import org.orekit.errors.OrekitMessages;
@@ -1027,8 +1028,8 @@ public class FieldCircularOrbit>
} else {
final T dt = circ.getDate().durationFrom(previousDate);
final T keplerAM = previousAlphaM .add(circ.getKeplerianMeanMotion().multiply(dt));
- continuousRAAN = normalizeAngle(circ.getRightAscensionOfAscendingNode(), previousRAAN);
- continuousAlphaM = normalizeAngle(circ.getAlphaM(), keplerAM);
+ continuousRAAN = MathUtils.normalizeAngle(circ.getRightAscensionOfAscendingNode(), previousRAAN);
+ continuousAlphaM = MathUtils.normalizeAngle(circ.getAlphaM(), keplerAM);
}
previousDate = circ.getDate();
previousRAAN = continuousRAAN;
@@ -1355,7 +1356,9 @@ public class FieldCircularOrbit>
* @param center center of the desired 2π interval for the result
* @param the type of the field elements
* @return a-2kπ with integer k and center-π <= a-2kπ <= center+π
+ * @deprecated replaced by {@link MathUtils#normalizeAngle(RealFieldElement, RealFieldElement)}
*/
+ @Deprecated
public static > T normalizeAngle(final T a, final T center) {
return a.subtract(2 * FastMath.PI * FastMath.floor((a.getReal() + FastMath.PI - center.getReal()) / (2 * FastMath.PI)));
}
diff --git a/src/main/java/org/orekit/orbits/FieldEquinoctialOrbit.java b/src/main/java/org/orekit/orbits/FieldEquinoctialOrbit.java
index 3cb8df806ea1b0a323fa1fa8728d0bf1a9701517..e21fe402564b6591b7266008637b15c0a2b3d468 100644
--- a/src/main/java/org/orekit/orbits/FieldEquinoctialOrbit.java
+++ b/src/main/java/org/orekit/orbits/FieldEquinoctialOrbit.java
@@ -30,6 +30,7 @@ import org.hipparchus.analysis.interpolation.FieldHermiteInterpolator;
import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
import org.hipparchus.util.FastMath;
import org.hipparchus.util.MathArrays;
+import org.hipparchus.util.MathUtils;
import org.orekit.errors.OrekitIllegalArgumentException;
import org.orekit.errors.OrekitInternalError;
import org.orekit.errors.OrekitMessages;
@@ -856,7 +857,7 @@ public class FieldEquinoctialOrbit> extends FieldO
} else {
final T dt = (T) equi.getDate().durationFrom(previousDate);
final T keplerLm = previousLm.add((T) equi.getKeplerianMeanMotion().multiply(dt));
- continuousLm = normalizeAngle((T) equi.getLM(), keplerLm);
+ continuousLm = MathUtils.normalizeAngle((T) equi.getLM(), keplerLm);
}
previousDate = equi.getDate();
previousLm = continuousLm;
@@ -1113,7 +1114,9 @@ public class FieldEquinoctialOrbit> extends FieldO
* @param center center of the desired 2π interval for the result
* @param the type of the field elements
* @return a-2kπ with integer k and center-π <= a-2kπ <= center+π
+ * @deprecated replaced by {@link MathUtils#normalizeAngle(RealFieldElement, RealFieldElement)}
*/
+ @Deprecated
public static > T normalizeAngle(final T a, final T center) {
return a.subtract(2 * FastMath.PI * FastMath.floor((a.getReal() + FastMath.PI - center.getReal()) / (2 * FastMath.PI)));
}
diff --git a/src/main/java/org/orekit/orbits/FieldKeplerianOrbit.java b/src/main/java/org/orekit/orbits/FieldKeplerianOrbit.java
index 6b26066fe5fff9890b09195eaf166b22538f43d3..1a441cf93ef68c5c8f347ab05120fe9d1143046c 100644
--- a/src/main/java/org/orekit/orbits/FieldKeplerianOrbit.java
+++ b/src/main/java/org/orekit/orbits/FieldKeplerianOrbit.java
@@ -32,6 +32,7 @@ import org.hipparchus.exception.MathIllegalArgumentException;
import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
import org.hipparchus.util.FastMath;
import org.hipparchus.util.MathArrays;
+import org.hipparchus.util.MathUtils;
import org.hipparchus.util.Precision;
import org.orekit.errors.OrekitIllegalArgumentException;
import org.orekit.errors.OrekitInternalError;
@@ -710,7 +711,7 @@ public class FieldKeplerianOrbit> extends FieldOrb
*/
public static > T meanToEllipticEccentric(final T M, final T e) {
// reduce M to [-PI PI) interval
- final T reducedM = normalizeAngle(M, M.getField().getZero());
+ final T reducedM = MathUtils.normalizeAngle(M, M.getField().getZero());
// compute start value according to A. W. Odell and R. H. Gooding S12 starter
T E;
@@ -1208,9 +1209,9 @@ public class FieldKeplerianOrbit> extends FieldOrb
} else {
final T dt = kep.getDate().durationFrom(previousDate);
final T keplerM = previousM.add(kep.getKeplerianMeanMotion().multiply(dt));
- continuousPA = normalizeAngle(kep.getPerigeeArgument(), previousPA);
- continuousRAAN = normalizeAngle(kep.getRightAscensionOfAscendingNode(), previousRAAN);
- continuousM = normalizeAngle(kep.getMeanAnomaly(), keplerM);
+ continuousPA = MathUtils.normalizeAngle(kep.getPerigeeArgument(), previousPA);
+ continuousRAAN = MathUtils.normalizeAngle(kep.getRightAscensionOfAscendingNode(), previousRAAN);
+ continuousM = MathUtils.normalizeAngle(kep.getMeanAnomaly(), keplerM);
}
previousDate = kep.getDate();
previousPA = continuousPA;
@@ -1743,7 +1744,9 @@ public class FieldKeplerianOrbit> extends FieldOrb
* @param center center of the desired 2π interval for the result
* @param the type of the field elements
* @return a-2kπ with integer k and center-π <= a-2kπ <= center+π
+ * @deprecated replaced by {@link MathUtils#normalizeAngle(RealFieldElement, RealFieldElement)}
*/
+ @Deprecated
public static > T normalizeAngle(final T a, final T center) {
return a.subtract(2 * FastMath.PI * FastMath.floor((a.getReal() + FastMath.PI - center.getReal()) / (2 * FastMath.PI)));
}
diff --git a/src/main/java/org/orekit/orbits/FieldOrbit.java b/src/main/java/org/orekit/orbits/FieldOrbit.java
index 775880b158fcb38ac37ab1ab461ccb64224f02ef..1c70348f492c1fe4947b5bcc730a134695ec4853 100644
--- a/src/main/java/org/orekit/orbits/FieldOrbit.java
+++ b/src/main/java/org/orekit/orbits/FieldOrbit.java
@@ -363,6 +363,9 @@ public abstract class FieldOrbit>
*/
public abstract boolean hasDerivatives();
+ /** Get the central attraction coefficient used for position and velocity conversions (m³/s²).
+ * @return central attraction coefficient used for position and velocity conversions (m³/s²)
+ */
public T getMu() {
return mu;
}
diff --git a/src/main/java/org/orekit/overview.html b/src/main/java/org/orekit/overview.html
index a71f5973020ed497eec3abe087d37930472104ba..8aef98cc4e501aa89671992c30ffa94fcd19bb69 100644
--- a/src/main/java/org/orekit/overview.html
+++ b/src/main/java/org/orekit/overview.html
@@ -54,7 +54,8 @@ discrete events. Here is a short list of the features offered by the library:composite transforms reduction and caching for efficiency
extensible central body shapes models (with predefined spherical and ellipsoidic shapes)
Cartesian and geodesic coordinates, kinematics
- Computation of Dilution Of Precision (DOP) with respect to GNSS constellations
+ computation of Dilution Of Precision (DOP) with respect to GNSS constellations
+ projection of sensor Field Of View footprint on ground for any FoV shape
Spacecraft state
@@ -288,12 +289,14 @@ discrete events. Here is a short list of the features offered by the library:
Customizable data loading
- - loading from local disk
+ - loading by exploring folders hierarchy on local disk
+ - loading from explicit lists of files on local disk
- loading from classpath
- loading from network (even through internet proxies)
- support for zip archives
- automatic decompression of gzip compressed (.gz) files upon loading
- automatic decompression of Unix compressed (.Z) files upon loading
+ - automatic decompression of Hatanaka compressed files upon loading
- plugin mechanism to add filtering like custom decompression algorithms, deciphering or monitoring
- plugin mechanism to delegate loading to user defined database or data access library
diff --git a/src/main/java/org/orekit/propagation/AbstractPropagator.java b/src/main/java/org/orekit/propagation/AbstractPropagator.java
index 3cd4d834e24a664c62414bfb343bf424a5d57e05..294292ae87746973ad8c26416e9c52849b3db7bf 100644
--- a/src/main/java/org/orekit/propagation/AbstractPropagator.java
+++ b/src/main/java/org/orekit/propagation/AbstractPropagator.java
@@ -19,6 +19,7 @@ package org.orekit.propagation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -31,6 +32,7 @@ import org.orekit.propagation.sampling.OrekitFixedStepHandler;
import org.orekit.propagation.sampling.OrekitStepHandler;
import org.orekit.propagation.sampling.OrekitStepNormalizer;
import org.orekit.time.AbsoluteDate;
+import org.orekit.utils.TimeSpanMap;
import org.orekit.utils.TimeStampedPVCoordinates;
/** Common handling of {@link Propagator} methods for analytical propagators.
@@ -61,6 +63,9 @@ public abstract class AbstractPropagator implements Propagator {
/** Additional state providers. */
private final List additionalStateProviders;
+ /** States managed by neither additional equations nor state providers. */
+ private final Map> unmanagedStates;
+
/** Initial state. */
private SpacecraftState initialState;
@@ -71,6 +76,7 @@ public abstract class AbstractPropagator implements Propagator {
stepHandler = null;
fixedStepSize = Double.NaN;
additionalStateProviders = new ArrayList();
+ unmanagedStates = new HashMap<>();
}
/** Set a start date.
@@ -177,7 +183,7 @@ public abstract class AbstractPropagator implements Propagator {
/** Update state by adding all additional states.
* @param original original state
* @return updated state, with all additional states included
- * @see #addAdditionalStateProvider(AdditionalStateProvider)
+ * @see #addAdditionalStateProvider(AdditionalStateProvider)
*/
protected SpacecraftState updateAdditionalStates(final SpacecraftState original) {
@@ -185,17 +191,10 @@ public abstract class AbstractPropagator implements Propagator {
// which may already contain additional states, for example in interpolated ephemerides
SpacecraftState updated = original;
- if (initialState != null) {
- // there is an initial state
- // (null initial states occur for example in interpolated ephemerides)
- // copy the additional states present in initialState but otherwise not managed
- for (final Map.Entry initial : initialState.getAdditionalStates().entrySet()) {
- if (!isAdditionalStateManaged(initial.getKey())) {
- // this additional state was in the initial state, but is unknown to the propagator
- // we simply copy its initial value as is
- updated = updated.addAdditionalState(initial.getKey(), initial.getValue());
- }
- }
+ // update the states not managed by providers
+ for (final Map.Entry> entry : unmanagedStates.entrySet()) {
+ updated = updated.addAdditionalState(entry.getKey(),
+ entry.getValue().get(original.getDate()));
}
// update the additional states managed by providers
@@ -266,4 +265,44 @@ public abstract class AbstractPropagator implements Propagator {
return propagate(date).getPVCoordinates(frame);
}
+ /** Initialize propagation.
+ * @since 10.1
+ */
+ protected void initializePropagation() {
+
+ unmanagedStates.clear();
+
+ if (initialState != null) {
+ // there is an initial state
+ // (null initial states occur for example in interpolated ephemerides)
+ // copy the additional states present in initialState but otherwise not managed
+ for (final Map.Entry initial : initialState.getAdditionalStates().entrySet()) {
+ if (!isAdditionalStateManaged(initial.getKey())) {
+ // this additional state is in the initial state, but is unknown to the propagator
+ // we store it in a way event handlers may change it
+ unmanagedStates.put(initial.getKey(), new TimeSpanMap<>(initial.getValue()));
+ }
+ }
+ }
+ }
+
+ /** Notify about a state change.
+ * @param state new state
+ */
+ protected void stateChanged(final SpacecraftState state) {
+ final AbsoluteDate date = state.getDate();
+ final boolean forward = date.durationFrom(getStartDate()) >= 0.0;
+ for (final Map.Entry changed : state.getAdditionalStates().entrySet()) {
+ final TimeSpanMap tsm = unmanagedStates.get(changed.getKey());
+ if (tsm != null) {
+ // this is an unmanaged state
+ if (forward) {
+ tsm.addValidAfter(changed.getValue(), date);
+ } else {
+ tsm.addValidBefore(changed.getValue(), date);
+ }
+ }
+ }
+ }
+
}
diff --git a/src/main/java/org/orekit/propagation/FieldAbstractPropagator.java b/src/main/java/org/orekit/propagation/FieldAbstractPropagator.java
index 754273a666a0a3718eb62ba4f9136f5f1dc7ded5..6b7cc8ed8dca9a30f6056b474a50aa7a741376f5 100644
--- a/src/main/java/org/orekit/propagation/FieldAbstractPropagator.java
+++ b/src/main/java/org/orekit/propagation/FieldAbstractPropagator.java
@@ -19,6 +19,7 @@ package org.orekit.propagation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -32,7 +33,9 @@ import org.orekit.propagation.events.FieldEventDetector;
import org.orekit.propagation.sampling.FieldOrekitFixedStepHandler;
import org.orekit.propagation.sampling.FieldOrekitStepHandler;
import org.orekit.propagation.sampling.FieldOrekitStepNormalizer;
+import org.orekit.time.AbsoluteDate;
import org.orekit.time.FieldAbsoluteDate;
+import org.orekit.utils.TimeSpanMap;
import org.orekit.utils.TimeStampedFieldPVCoordinates;
/** Common handling of {@link Propagator} methods for analytical propagators.
@@ -63,6 +66,9 @@ public abstract class FieldAbstractPropagator> imp
/** Additional state providers. */
private final List> additionalStateProviders;
+ /** States managed by neither additional equations nor state providers. */
+ private final Map> unmanagedStates;
+
/** Field used.*/
private final Field field;
@@ -77,7 +83,8 @@ public abstract class FieldAbstractPropagator> imp
stepHandler = null;
this.field = field;
fixedStepSize = field.getZero().add(Double.NaN);
- additionalStateProviders = new ArrayList>();
+ additionalStateProviders = new ArrayList<>();
+ unmanagedStates = new HashMap<>();
}
/** Set a start date.
@@ -182,7 +189,7 @@ public abstract class FieldAbstractPropagator> imp
/** Update state by adding all additional states.
* @param original original state
* @return updated state, with all additional states included
- * @see #addAdditionalStateProvider(FieldAdditionalStateProvider)
+ * @see #addAdditionalStateProvider(FieldAdditionalStateProvider)
*/
protected FieldSpacecraftState updateAdditionalStates(final FieldSpacecraftState original) {
@@ -190,18 +197,10 @@ public abstract class FieldAbstractPropagator> imp
// which may already contain additional states, for example in interpolated ephemerides
FieldSpacecraftState updated = original;
- if (initialState != null) {
- // there is an initial state
- // (null initial states occur for example in interpolated ephemerides)
- // copy the additional states present in initialState but otherwise not managed
- for (final Map.Entry initial : initialState.getAdditionalStates().entrySet()) {
-
- if (!isAdditionalStateManaged(initial.getKey())) {
- // this additional state was in the initial state, but is unknown to the propagator
- // we simply copy its initial value as is
- updated = updated.addAdditionalState(initial.getKey(), initial.getValue());
- }
- }
+ // update the states not managed by providers
+ for (final Map.Entry> entry : unmanagedStates.entrySet()) {
+ updated = updated.addAdditionalState(entry.getKey(),
+ entry.getValue().get(original.getDate().toAbsoluteDate()));
}
// update the additional states managed by providers
@@ -273,4 +272,44 @@ public abstract class FieldAbstractPropagator> imp
return propagate(date).getPVCoordinates(frame);
}
+ /** Initialize propagation.
+ * @since 10.1
+ */
+ protected void initializePropagation() {
+
+ unmanagedStates.clear();
+
+ if (initialState != null) {
+ // there is an initial state
+ // (null initial states occur for example in interpolated ephemerides)
+ // copy the additional states present in initialState but otherwise not managed
+ for (final Map.Entry initial : initialState.getAdditionalStates().entrySet()) {
+ if (!isAdditionalStateManaged(initial.getKey())) {
+ // this additional state is in the initial state, but is unknown to the propagator
+ // we store it in a way event handlers may change it
+ unmanagedStates.put(initial.getKey(), new TimeSpanMap<>(initial.getValue()));
+ }
+ }
+ }
+ }
+
+ /** Notify about a state change.
+ * @param state new state
+ */
+ protected void stateChanged(final FieldSpacecraftState state) {
+ final AbsoluteDate date = state.getDate().toAbsoluteDate();
+ final boolean forward = date.durationFrom(getStartDate().toAbsoluteDate()) >= 0.0;
+ for (final Map.Entry changed : state.getAdditionalStates().entrySet()) {
+ final TimeSpanMap tsm = unmanagedStates.get(changed.getKey());
+ if (tsm != null) {
+ // this is an unmanaged state
+ if (forward) {
+ tsm.addValidAfter(changed.getValue(), date);
+ } else {
+ tsm.addValidBefore(changed.getValue(), date);
+ }
+ }
+ }
+ }
+
}
diff --git a/src/main/java/org/orekit/propagation/FieldPropagator.java b/src/main/java/org/orekit/propagation/FieldPropagator.java
index 0b568479b1381c2177a8fe7e8c2d00d1bb74e3ee..8a539aa5ff10e1e477bb78db110c0341ca552475 100644
--- a/src/main/java/org/orekit/propagation/FieldPropagator.java
+++ b/src/main/java/org/orekit/propagation/FieldPropagator.java
@@ -175,7 +175,11 @@ public interface FieldPropagator> extends FieldPVC
* Additional states that are present in the {@link #getInitialState() initial state}
* but have no evolution method registered are not considered as managed states.
* These unmanaged additional states are not lost during propagation, though. Their
- * value will simply be copied unchanged throughout propagation.
+ * value are piecewise constant between state resets that may change them if some
+ * event handler {@link
+ * org.orekit.propagation.events.handlers.FieldEventHandler#resetState(FieldEventDetector,
+ * FieldSpacecraftState) resetState} method is called at an event occurrence and happens
+ * to change the unmanaged additional state.
*
* @param name name of the additional state
* @return true if the additional state is managed
diff --git a/src/main/java/org/orekit/propagation/FieldSpacecraftState.java b/src/main/java/org/orekit/propagation/FieldSpacecraftState.java
index 5d6e94515574215c9360333e6db7dfe3559709f6..7a4d0b37b16a06a26723789bf9bc7c037a0c94ae 100644
--- a/src/main/java/org/orekit/propagation/FieldSpacecraftState.java
+++ b/src/main/java/org/orekit/propagation/FieldSpacecraftState.java
@@ -424,10 +424,11 @@ public class FieldSpacecraftState >
final Map newMap = new HashMap(additional.size() + 1);
newMap.putAll(additional);
newMap.put(name, value.clone());
- if (absPva == null)
+ if (absPva == null) {
return new FieldSpacecraftState<>(orbit, attitude, mass, newMap);
- else
+ } else {
return new FieldSpacecraftState<>(absPva, attitude, mass, newMap);
+ }
}
/** Check orbit and attitude dates are equal.
diff --git a/src/main/java/org/orekit/propagation/Propagator.java b/src/main/java/org/orekit/propagation/Propagator.java
index 1b83b05c37f6c03476c984455bb72d3b7751553b..6769d83d763c3b5d4807f2e708edbfa33673e4ba 100644
--- a/src/main/java/org/orekit/propagation/Propagator.java
+++ b/src/main/java/org/orekit/propagation/Propagator.java
@@ -209,7 +209,11 @@ public interface Propagator extends PVCoordinatesProvider {
* Additional states that are present in the {@link #getInitialState() initial state}
* but have no evolution method registered are not considered as managed states.
* These unmanaged additional states are not lost during propagation, though. Their
- * value will simply be copied unchanged throughout propagation.
+ * value are piecewise constant between state resets that may change them if some
+ * event handler {@link
+ * org.orekit.propagation.events.handlers.EventHandler#resetState(EventDetector,
+ * SpacecraftState) resetState} method is called at an event occurrence and happens
+ * to change the unmanaged additional state.
*
* @param name name of the additional state
* @return true if the additional state is managed
diff --git a/src/main/java/org/orekit/propagation/SpacecraftState.java b/src/main/java/org/orekit/propagation/SpacecraftState.java
index a9191e9fdba43844bda557e36a1047348face248..f80bf8fe435bf5f445dfa487c3026e3bd664fa4b 100644
--- a/src/main/java/org/orekit/propagation/SpacecraftState.java
+++ b/src/main/java/org/orekit/propagation/SpacecraftState.java
@@ -341,10 +341,11 @@ public class SpacecraftState
final Map newMap = new HashMap(additional.size() + 1);
newMap.putAll(additional);
newMap.put(name, value.clone());
- if (absPva == null)
+ if (absPva == null) {
return new SpacecraftState(orbit, attitude, mass, newMap);
- else
+ } else {
return new SpacecraftState(absPva, attitude, mass, newMap);
+ }
}
/** Check orbit and attitude dates are equal.
@@ -435,12 +436,13 @@ public class SpacecraftState
* except for the mass and additional states which stay unchanged
*/
public SpacecraftState shiftedBy(final double dt) {
- if (absPva == null)
+ if (absPva == null) {
return new SpacecraftState(orbit.shiftedBy(dt), attitude.shiftedBy(dt),
mass, additional);
- else
+ } else {
return new SpacecraftState(absPva.shiftedBy(dt), attitude.shiftedBy(dt),
mass, additional);
+ }
}
/** {@inheritDoc}
diff --git a/src/main/java/org/orekit/propagation/analytical/AbstractAnalyticalPropagator.java b/src/main/java/org/orekit/propagation/analytical/AbstractAnalyticalPropagator.java
index 2329d0e2f8ab85cbb3398e22cca319e865a3fcce..87bcd2a3183f6a918a223aac26b03a1338a9dc2c 100644
--- a/src/main/java/org/orekit/propagation/analytical/AbstractAnalyticalPropagator.java
+++ b/src/main/java/org/orekit/propagation/analytical/AbstractAnalyticalPropagator.java
@@ -116,6 +116,8 @@ public abstract class AbstractAnalyticalPropagator extends AbstractPropagator {
public SpacecraftState propagate(final AbsoluteDate start, final AbsoluteDate target) {
try {
+ initializePropagation();
+
lastPropagationStart = start;
final double dt = target.durationFrom(start);
diff --git a/src/main/java/org/orekit/propagation/analytical/AggregateBoundedPropagator.java b/src/main/java/org/orekit/propagation/analytical/AggregateBoundedPropagator.java
index 72b820580dd2531bcfd29521671f2c83cc521220..a199369f2fdc1c30a51758177b352c99ac0a126a 100644
--- a/src/main/java/org/orekit/propagation/analytical/AggregateBoundedPropagator.java
+++ b/src/main/java/org/orekit/propagation/analytical/AggregateBoundedPropagator.java
@@ -21,6 +21,7 @@ import java.util.Map.Entry;
import java.util.NavigableMap;
import java.util.TreeMap;
+import org.orekit.attitudes.Attitude;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.frames.Frame;
@@ -71,6 +72,27 @@ public class AggregateBoundedPropagator extends AbstractAnalyticalPropagator
this.propagators.firstEntry().getValue().getInitialState());
}
+ @Override
+ protected SpacecraftState basicPropagate(final AbsoluteDate date) {
+ // #589 override this method for a performance benefit,
+ // getPropagator(date).propagate(date) is only called once
+
+ // do propagation
+ final SpacecraftState state = getPropagator(date).propagate(date);
+
+ // evaluate attitude
+ final Attitude attitude =
+ getAttitudeProvider().getAttitude(this, date, state.getFrame());
+
+ // build raw state
+ if (state.isOrbitDefined()) {
+ return new SpacecraftState(
+ state.getOrbit(), attitude, state.getMass(), state.getAdditionalStates());
+ } else {
+ return new SpacecraftState(
+ state.getAbsPVA(), attitude, state.getMass(), state.getAdditionalStates());
+ }
+ }
@Override
public TimeStampedPVCoordinates getPVCoordinates(final AbsoluteDate date,
diff --git a/src/main/java/org/orekit/propagation/analytical/EcksteinHechlerPropagator.java b/src/main/java/org/orekit/propagation/analytical/EcksteinHechlerPropagator.java
index 54f4182b1b5f3217964e35e316bdd4ecc11adc02..1225a8bc70a637ef9e5c95b8a00c72047a648cf3 100644
--- a/src/main/java/org/orekit/propagation/analytical/EcksteinHechlerPropagator.java
+++ b/src/main/java/org/orekit/propagation/analytical/EcksteinHechlerPropagator.java
@@ -313,6 +313,7 @@ public class EcksteinHechlerPropagator extends AbstractAnalyticalPropagator {
} else {
models.addValidBefore(newModel, state.getDate());
}
+ stateChanged(state);
}
/** Compute mean parameters according to the Eckstein-Hechler analytical model.
diff --git a/src/main/java/org/orekit/propagation/analytical/FieldAbstractAnalyticalPropagator.java b/src/main/java/org/orekit/propagation/analytical/FieldAbstractAnalyticalPropagator.java
index da303dde6adc94b7426dd9914cbbe6dec4b35fe7..2564fa85ca78fd3030eb5660dceec3a36a722cfe 100644
--- a/src/main/java/org/orekit/propagation/analytical/FieldAbstractAnalyticalPropagator.java
+++ b/src/main/java/org/orekit/propagation/analytical/FieldAbstractAnalyticalPropagator.java
@@ -115,6 +115,9 @@ public abstract class FieldAbstractAnalyticalPropagator propagate(final FieldAbsoluteDate start, final FieldAbsoluteDate target) {
try {
+
+ initializePropagation();
+
lastPropagationStart = start;
final T dt = target.durationFrom(start);
@@ -170,8 +173,6 @@ public abstract class FieldAbstractAnalyticalPropagator> exten
} else {
models.addValidBefore(newModel, state.getDate());
}
+ stateChanged(state);
}
/** Compute mean parameters according to the Eckstein-Hechler analytical model.
@@ -330,10 +332,10 @@ public class FieldEcksteinHechlerPropagator> exten
final T deltaEx = osculating.getCircularEx().subtract(parameters[1].getValue());
final T deltaEy = osculating.getCircularEy().subtract(parameters[2].getValue());
final T deltaI = osculating.getI() .subtract(parameters[3].getValue());
- final T deltaRAAN = normalizeAngle(osculating.getRightAscensionOfAscendingNode().subtract(
+ final T deltaRAAN = MathUtils.normalizeAngle(osculating.getRightAscensionOfAscendingNode().subtract(
parameters[4].getValue()),
zero);
- final T deltaAlphaM = normalizeAngle(osculating.getAlphaM().subtract(parameters[5].getValue()), zero);
+ final T deltaAlphaM = MathUtils.normalizeAngle(osculating.getAlphaM().subtract(parameters[5].getValue()), zero);
// update mean parameters
current = new FieldEHModel<>(factory,
new FieldCircularOrbit<>(current.mean.getA().add(deltaA),
@@ -607,13 +609,13 @@ public class FieldEcksteinHechlerPropagator> exten
// right ascension of ascending node
final FieldDerivativeStructure omm =
- factory.build(normalizeAngle(mean.getRightAscensionOfAscendingNode().add(ommD.multiply(xnot.getValue())),
+ factory.build(MathUtils.normalizeAngle(mean.getRightAscensionOfAscendingNode().add(ommD.multiply(xnot.getValue())),
zero.add(FastMath.PI)),
ommD.multiply(xnotDot),
zero);
// latitude argument
final FieldDerivativeStructure xlm =
- factory.build(normalizeAngle(mean.getAlphaM().add(aMD.multiply(xnot.getValue())), zero.add(FastMath.PI)),
+ factory.build(MathUtils.normalizeAngle(mean.getAlphaM().add(aMD.multiply(xnot.getValue())), zero.add(FastMath.PI)),
aMD.multiply(xnotDot),
zero);
@@ -816,7 +818,9 @@ public class FieldEcksteinHechlerPropagator> exten
* @param