diff --git a/src/site/markdown/tutorials/direct-localization.md b/src/site/markdown/tutorials/direct-localization.md new file mode 100644 index 0000000000000000000000000000000000000000..f2da7b7ef008519515d4b32c49f8d1cb29962b4a --- /dev/null +++ b/src/site/markdown/tutorials/direct-localization.md @@ -0,0 +1,241 @@ +<!--- Copyright 2013-2014 CS Systèmes d'Information + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +# Rugged initialization and direct localization + +This tutorial explains how to initialize Rugged and use it to geolocate a satellite image. +Let's imagine the sensor is a single line imager with 2000 pixels and 20° field of view, +oriented 10° off nadir. GPS and AOCS auxiliary data are available and provide us with a +list of positions, velocities and attitude quaternions recorded during the acquisition. By +passing all this information to Rugged, we will be able to precisely locate each point of +the image on the Earth. Well, not exactly precise, as this first tutorial does not use a +Digital Elevation Model, but considers the Earth as an ellipsoid. The DEM will be added in +a second tutorial. The objective here is limited to explain how to initialize everything +Rugged needs to know about the sensor and the acquisition. + + +## Sensor's definition + + +Let's start by defining the imager. The sensor model is described by its viewing geometry +(i.e. the line of sight of each physical pixel) and by its datation model. + + +### Line of sight + + +We need to define the line of sight (LOS) vector coordinates of each sensor pixel in the +satellite frame; X axis is parallel to the velocity vector in the absence of steering, Z is +pointing towards the Earth and Y is such that X,Y,Z forms a right-handed coordinate system. + +For this we need the following packages + + import java.util.ArrayList; + import java.util.List; + import org.apache.commons.math3.geometry.euclidean.threed.Rotation; + import org.apache.commons.math3.geometry.euclidean.threed.Vector3D; + import org.apache.commons.math3.util.FastMath; + import org.orekit.rugged.api.TimeDependentLOS; + import org.orekit.rugged.api.FixedLOS; + + +The viewing direction of pixel i with respect to the instrument is defined by the vector: + + Vector3D viewDir = new Vector3D(0d, i*FastMath.toRadians(20)/2000d, 1d).normalize(); //20° field of view, 2000 pixels + + +The instrument is oriented 10° off nadir around the X-axis, we need to rotate the viewing +direction to obtain the line of sight in the satellite frame + + Rotation rotation = new Rotation(axis, FastMath.toRadians(10)); + viewDir = rotation.applyTo(viewDir); + +The viewing direction is the line of sight of one pixel. In the case of a single line sensor, +Rugged expects to receive an array of LOS. The LOS can be time dependent (useful when applying +time-dependent corrections to the viewing angles). Here we will suppose that the viewing +directions are constant with time. In consequence we can use the object `FixedLOS` which is +instantiated from the viewing direction vector. + + List<TimeDependentLOS> lineOfSight = new ArrayList<TimeDependentLOS>(); + lineOfSight.add(new FixedLOS(los)); + + +### Datation model + +The datation model gives the line number in the image as a function of time, and vice-versa the +time as a function of the line number. Here we are using a linear datation model (line sampling +rate is constant) + + +We use Orekit for handling time and dates, and Rugged for defining the datation model: + + import org.orekit.time.AbsoluteDate; + import org.orekit.time.TimeScalesFactory; + import org.orekit.rugged.api.LinearLineDatation; + AbsoluteDate absDate = new AbsoluteDate("2009-12-11T10:49:55.899994", TimeScalesFactory.getGPS()); + LinearLineDatation lineDatation = new LinearLineDatation(absDate, 1d, 20); + +The LinearLineDataion class is instanciated with 3 arguments: the first is the reference date, +the second is the line number at the reference date, and the last argument is the line rate (20 +lines per second in this example) . + +Note that Orekit can handle many different time scales through TimeScalesFactory, so we do not +need to worry about conversion. + +### Line sensor + +With the LOS and the datation now defined , we can initialize a line sensor object in Rugged: + + import org.orekit.rugged.api.LineSensor; + LineSensor lineSensor = new LineSensor("mySensor", lineDatation, Vector3D.ZERO, lineOfSight); + +The first parameter is the nickname of the sensor. It is necessary because we can define multiple +sensors (useful for an imager with several spectral channels or detector arrays). The third argument +is the sensor position relative to the centre of the satellite reference frame. Here it is set to 0. + + +## Satellite position, velocity and attitude + + +As input to Rugged, we will need to pass sufficient information to describe the position and +orientation of the platform during the acquisition. In our example, the list of positions, velocities +and quaternions are hard-coded. In real life, we would extract GPS and AOCS data from the satellite +auxiliary telemetry. + +Note that for simulation purpose, we could also use Orekit to simulate the orbit. It offers very +convenient functions to propagate sun-synchronous orbits with yaw steering compensation (typical +orbits for Earth Observation satellites). + + +### Reference frames + + +Rugged expects the positions, velocities and quaternions to be expressed in an inertial frame. +All standard inertial and terrestrial frames are implemented in Orekit, so we just need to specify +which frames we are using and convert if necessary. + +Conversion from inertial to Earth-rotating frame is transparent to the user and is based on the most +recent precession/nutation model on top of which corrections published by the IERS are applied. IERS +bulletins and other physical data are provided within the orekit-data folder. There are several ways +to configure Orekit to use this data. More information is given +[here](https://www.orekit.org/forge/projects/orekit/wiki/Configuration). + +In our application, we simply need to know the name of the frames we are working with. Position and +velocities are given in the ITRF terrestrial frame, while the quaternions are given in EME2000 +inertial frame. + + import org.orekit.frames.Frame; + import org.orekit.frames.FramesFactory; + Frame eme2000 = FramesFactory.getEME2000(); + boolean simpleEOP = true; // we don't want to compute tiny tidal effects at millimeter level + Frame itrf = FramesFactory.getITRF(IERSConventions.IERS_2010, simpleEOP); + + +### Satellite attitude + + +The attitude quaternions are grouped in a list of TimeStampedAngularCoordinates objects: + + import org.orekit.utils.TimeStampedAngularCoordinates; + List<TimeStampedAngularCoordinates> satelliteQList = new ArrayList<TimeStampedAngularCoordinates>();` + +Each attitude sample (quaternion, time) is added to the list + + AbsoluteDate attitudeDate = new AbsoluteDate(gpsDateAsString, TimeScalesFactory.getGPS()); + Rotation rotation = new Rotation(q0, q1, q2, q3, true); //q0 is the scalar term + Vector3D rotationRate = Vector3D.ZERO; + TimeStampedAngularCoordinates pair = new TimeStampedAngularCoordinates(attitudeDate, rotation, rotationRate); + satelliteQList.add(pair);` + +### Position and velocities + + +Similarly the position and velocities will be set in a list of `TimeStampedPVCoordinates`. Before being +added to the list, they must be transformed to EME2000: + + List<TimeStampedPVCoordinates> satellitePVList = new ArrayList<TimeStampedPVCoordinates>(); + AbsoluteDate ephemerisDate = new AbsoluteDate(gpsDateAsString, TimeScalesFactory.getGPS()); + Vector3D position = new Vector3D(px, py, pz); + Vector3D velocity = new Vector3D(vx, vy, vz); + PVCoordinates pvITRF = new PVCoordinates(position, velocity); + Transform transform = itrf.getTransformTo(eme2000, ephemerisDate); + PVCoordinates pvEME2000 = transform.transformPVCoordinates(pITRF); + satellitePVList.add(new TimeStampedPVCoordinates(ephemerisDate, pEME2000, vEME2000)); + + +## Rugged initialization + + +Finally we can initialize Rugged. It looks like this: + + import org.orekit.rugged.api.AlgorithmId; + import org.orekit.rugged.api.BodyRotatingFrameId; + import org.orekit.rugged.api.EllipsoidId; + import org.orekit.rugged.api.InertialFrameId; + import org.orekit.rugged.api.Rugged; + import org.orekit.rugged.api.RuggedException; + import org.orekit.utils.AngularDerivativesFilter; + import org.orekit.utils.CartesianDerivativesFilter; + import org.orekit.utils.IERSConventions; + Rugged rugged = new Rugged (demTileUpdater, nbTiles, demAlgoId, + EllipsoidId.WGS84, InertialFrameId.EME2000, BodyRotatingFrameId.ITRF, + acquisitionStartDate, acquisitionStopDate, timeTolerance, + satellitePVList, nbPVPoints, CartesianDerivativesFilter.USE_P, + satelliteQList, nbQPoints, AngularDerivativesFilter.USE_R, 0.1); + +Argh, that sounds complicated. It is not so difficult since we have already defined most of what is +needed. Let's describe the arguments line by line: + +The first 3 are related to the DEM. Since we are ignoring the DEM, they can be set respectively to +`null`, `0` and `AlgorithmId.IGNORE_DEM_USE_ELLIPSOID`. + +The next 3 arguments defines the ellipsoid and the reference frames: `EllipsoidId.WGS84`, +`InertialFrameId.EME2000`, `BodyRotatingFrameId.ITRF` + +On the third line, we find the time interval of the acquisition: acquisitionStartDate, acquisitionStopDate, +timeTolerance (margin allowed for extrapolation, in seconds). This is an important information as Rugged +will pre-compute a lot of stuff at initialization in order to optimize further calls to the direct and +inverse localization functions. + +On the fourth line, the arguments are the list of time-stamped positions and velocities as well as options +for interpolation: number of points to use and type of filter for derivatives. We find the same arguments +on the last line for the attitude quaternions. + + +The sensor models are added after initialization. We can add as many as we want. + + rugged.addLineSensor(lineSensor); + + + +## Direct localization + +Finally everything is set to do some real work. Let's try to locate a point on Earth + +Upper left point (first line, first pixel): + + Vector3D position = lineSensor.getPosition(); // This returns a zero vector since we set the relative position of the sensor w.r.T the satellite to 0. + AbsoluteDate firstLineDate = lineSensor.getDate(1); + Vector3D los = lineSensor.getLos(firstLineDate, 0); + GeodeticPoint upLeftPoint = rugged.directLocalization(firstLineDate, position, los); + + +The results are .... + + + + +## Source code + +The source code is available in .... diff --git a/src/site/site.xml b/src/site/site.xml index 9f47d4e4fa494fab48a348825ab189fcea67eeae..e7e92649b0a9312fbb0fb7f9d9469f99479791a0 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -41,6 +41,9 @@ <item name="Digital Elevation Model" href="/design/digital-elevation-model.html" /> <item name="Preliminary design" href="/design/preliminary-design.html" /> </menu> + <menu name="Tutorials"> + <item name="Direct localization" href="/tutorials/direct-localization.html" /> + </menu> <menu name="Development"> <item name="Contributing" href="/contributing.html" /> <item name="Guidelines" href="/guidelines.html" />