INS and GNSS#
This page gives the models of the GNSS receiver, the IMU and the INS, with the datum chain and the UTM projection. It also gives the INS of ACRES Core, the parameters and the seeds of the noise.
Scope and Assumptions#
The game has three position and motion sensors. Each sensor reads the state of the chassis at the physics step.
| Sensor | Model | Output |
|---|---|---|
| GNSS receiver | A receiver model in the position domain: sky view, fix states, correlated errors, datum chain. | gnss.jsonl |
| IMU | Specific force and angular rate with a constant bias and white noise. | imu.jsonl |
| INS | The OxTS-style unit of the Polaris: fix, IMU, velocity and UTM odometry at one point. | ins.jsonl, the sensor stream |
The models use these assumptions.
- The GNSS receiver has no pseudoranges and no carrier phases. The model adds an error to the true antenna position.
- The satellites are a synthetic constellation on circular orbits. The constellation is not a broadcast almanac.
- The models apply the position errors in the grid ENU frame of the tile. The models ignore the convergence of that grid.
- The IMU has no bias instability, no scale-factor error, no misalignment and no quantisation.
- The INS is the truth with errors. It has no inertial mechanisation, no alignment and no GNSS outage.
- The sensors do not apply forces to the vehicle.
The sensor stream and the ROS 2 topics carry the INS only. The GNSS receiver and the IMU write files only. The Sensor Stream page gives the binary frame of an INS epoch. The Camera page and the LiDAR page give the other sensors.
Symbols#
| Symbol | Quantity | Unit |
|---|---|---|
| \(t\) | Episode time: the physics steps since the start of the recording, divided by 120 | s |
| \(\Delta t\) | Time since the previous epoch of the same sensor | s |
| \(\mathbf{p}\), \(\mathbf{R}\) | Position of the chassis origin and rotation from the body frame to the world frame | m, - |
| \(\mathbf{v}\), \(\pmb{\omega}\) | Linear velocity of the centre of mass and angular velocity of the chassis, world axes | m/s, rad/s |
| \(\mathbf{r}_a\), \(\mathbf{r}_i\), \(\mathbf{r}_n\) | Lever arm of the antenna, the IMU and the INS point, body FLU | m |
| \(\mathbf{l}\) | Unit line of sight to a satellite, ENU | - |
| \(N_u\), \(N_c\), \(N_n\), \(N_f\) | Satellites: used, clear, near an obstruction, through foliage | - |
| \(H\), \(V\), \(P\) | HDOP, VDOP and PDOP of the used satellites | - |
| \(H_o\), \(V_o\) | HDOP and VDOP of all satellites above the elevation mask | - |
| \(\rho\), \(m\) | Reflector index and multipath index | - |
| \(a_c\) | Age of the newest correction message | s |
| \(\sigma_{gm}\), \(\sigma_w\), \(\sigma_{mp}\) | Standard deviation of the Gauss-Markov, white and multipath error terms | m |
| \(u\), \(z\) | Unit Gauss-Markov states of the correlated error and of the multipath error | - |
| \(g\) | Standard normal value from the generator | - |
| \(\tau\), \(\tau_m\) | Correlation time of the fix state and of the multipath | s |
| \(k_H\), \(k_V\) | Horizontal and vertical DOP ratio | - |
| \(\varphi\), \(\lambda\), \(h\) | Latitude, longitude and ellipsoidal height | rad, rad, m |
| \(N_g\) | Geoid undulation | m |
| \(\mathbf{f}\) | Specific force | m/s² |
| \(\mathbf{b}_a\), \(\mathbf{b}_g\) | Bias of the accelerometer and of the gyroscope | m/s², rad/s |
| \(E\), \(N\) | UTM easting and northing | m |
| \(\gamma\), \(k\) | Meridian convergence and point scale factor of the UTM projection | rad, - |
| \(\alpha\) | Grid rotation: the UTM azimuth of the grid north of the simulator | rad |
Frames and Sample Times#
The recorder converts the Unreal frames to right-handed frames at its boundary. The function names are in AcresSensors.cpp.
| Frame | Axes | Conversion from Unreal |
|---|---|---|
| ENU | East, north, up, metres. The origin is the origin of the tile. | Vector: \((x, -y, z)\). Angular rate: \((-x, y, -z)\). Functions Enu, EnuAngular. |
| Body FLU | Forward, left, up, metres. The origin is the chassis origin. | Vector: \((x, -y, z)\). Angular rate: \((-x, y, -z)\). Functions Flu, FluAngular. |
The ENU axes are the grid axes of the survey grid EPSG:2968. They are not a geodetic tangent frame.
The attitude in a row is the active rotation from FLU to ENU as a quaternion in the sequence x, y, z, w (EnuQuat).
A mount in the frame base_footprint is a mount from the middle of the rear axle on the ground.
The chassis origin is 0.4 of the wheelbase in front of that point, at the height of the centre of gravity.
A sensor with the rate \(f\) has its first sample at \(t = 0\). The function Due then adds \(1/f\) to the due time after each sample.
A rate that does not divide 120 Hz thus gives intervals of two different lengths. For example, the INS at 100 Hz gives intervals of one physics step and of two physics steps. The mean rate stays 100 Hz.
The rows of the GNSS receiver, the IMU and the INS have a modelled latency (Emit, Drain).
\(T_d\) is delay_s. The recorder writes a row at the first physics step with \(t \ge t_{available}\).
The row contains sample_time_s, available_time_s and delivery_time_s.
The sensor stream sends an INS epoch immediately, without this latency.
Seeds and Determinism#
Each noise source has its own generator FAcresPcg32 (PCG-XSH-RR, O'Neill 2014). The same seed gives the same noise.
The seed procedure is pcg32_srandom_r: set \(s = 0\) and \(c = 2q + 1\), do one step, add the seed to \(s\), do one step.
\(q\) is the sequence number of the stream.
A uniform value uses one output. A Gaussian value uses two uniform values (Box-Muller, cosine branch).
| Stream | Seed | Sequence | Values |
|---|---|---|---|
GNSS errors (RngGnss) |
seed |
1 | 11 for each epoch |
IMU (RngImu) |
seed + 1009 |
2 | 6 Gaussian values for the biases at the start, then 6 for each sample |
| GNSS constellation | seed + 3027 |
4 | 1 uniform value, then 2 for each system and 1 for each satellite |
INS (RngIns) |
seed + 5045 |
6 | 18 Gaussian values for each epoch |
The LiDAR page and the Camera page give the streams of those sensors.
A model uses the same number of values in each epoch, in all states.
Thus the option -SensorNoNoise and a change of the fix state do not move the values of the subsequent epochs.
seed is the key of sensors.json. The option -SensorSeed= replaces it.
Note
In a session with more than one vehicle, each vehicle in the game uses the same seeds. ACRES Core adds 7919 times the agent index to the seed of the INS stream.
GNSS Receiver#
The source is FAcresGnssModel in AcresGnssModel.cpp. The recorder calls Epoch at the rate gnss.hz on the physics thread.
Antenna Position#
The antenna is at the lever arm \(\mathbf{r}_a\) (gnss.antenna_flu_m) from the chassis origin.
The model uses \(\mathbf{p}_a\) in Unreal metres for the sky view and in ENU metres for the output.
Constellation#
Initialize builds the satellites one time. Each satellite has a circular orbit.
| System | Planes | Satellites in a Plane | Orbit Radius | Inclination |
|---|---|---|---|---|
| GPS | 6 | 5 | 26 559.7 km | 55° |
| Galileo | 3 | 8 | 29 599.8 km | 56° |
| GLONASS | 3 | 8 | 25 508.0 km | 64.8° |
The first uniform value gives a time offset \(t_0 = 86400\, U\). Each system then gets two offsets, \(\Omega_0 = 2\pi U\) and \(u_{00} = 2\pi U\). A system that is off uses its values also. Satellite \(j\) of plane \(i\) in a system with \(n_p\) planes and \(n_s\) satellites in a plane has these elements.
\(\mu = 3.986004418 \times 10^{14}\) m³/s² and \(a\) is the orbit radius. At the time \(t\) the argument of latitude is \(u = u_0 + n t\).
\(\omega_E = 7.2921151467 \times 10^{-5}\) rad/s and \(i_o\) is the inclination. \(\mathbf{x}_{site}\) is the ECEF position of the origin of the tile on the WGS84 ellipsoid. The site latitude \(\varphi_s\) and longitude \(\lambda_s\) rotate the vector into ENU. The model then normalises it.
The model keeps a satellite when its elevation \(\arcsin(l_u)\) is not less than elevation_mask_deg.
Sky Obstruction#
GatherStructures and the recorder collect the obstacles at the start of a recording.
| Obstacle | Shape | Source |
|---|---|---|
| Building | Box. The axis-aligned bounds of the actor. | Actors with the tag ACREBuildingVisual |
| Grain bin | Vertical cylinder. The radius is the smaller horizontal half extent. | Actors with the tag ACRESiloVisual |
| Tree crown | Sphere | The tree crown proxies of the LiDAR |
The box of a building that is not parallel to the grid is larger than the building.
For each epoch, the model first keeps the obstacles that can come above the mask.
\(D\) is the horizontal distance from the antenna to the obstacle, \(z_t\) is its top and \(d_n\) is near_margin_m.
The model then tests the ray from the antenna along \(\mathbf{l}\) against each kept obstacle.
| Result | Condition | Effect |
|---|---|---|
| Blocked | The ray hits a box or a cylinder. Or the path in a tree crown is foliage_block_path_m or longer. |
The receiver does not use the satellite. |
| Foliage | The ray goes through a tree crown on a shorter path. | Used. Counts in \(N_f\). |
| Near | The ray hits the box or the cylinder that is larger by near_margin_m. |
Used. Counts in \(N_n\). |
| Clear | No other result applies. | Used. Counts in \(N_c\). |
\(N_u = N_c + N_n + N_f\). The ray tests are RayBox (slab test), RayCylinder and SphereChord.
Dilution of Precision#
Dop builds the geometry matrix \(\mathbf{G}\) with one row \([-l_e, -l_n, -l_u, 1]\) for each satellite.
The three values are 99.99 with less than 4 satellites or with a singular matrix. The model computes \(H, V, P\) from the used satellites and \(H_o, V_o\) from all satellites above the mask.
Multipath Index#
Each solid obstacle within reflector_range_m (\(d_r\)) adds to the reflector index \(\rho\). \(b_h\) is the half width of the obstacle.
\(b_h\) is the radius of a cylinder, or half of the horizontal diagonal of a box. The multipath index is as follows.
Correction Stream#
The base station makes one correction message each interval_s (\(T_c\)). A message arrives latency_s (\(T_l\)) later.
CorrectionAge finds the newest message that arrived.
The receiver does not get a message with its generation time \(j T_c\) in an outage interval \([t_1, t_2)\). The model then steps back to the last message before the outage.
With the start mode cold, the first message is \(j = 0\). With no message, the age is not available and the row shows null.
Fix State Machine#
The model examines these conditions in sequence for each epoch.
| Sequence | Condition | Fix State |
|---|---|---|
| 1 | Cold start with \(t\) less than cold_ttff_s, or \(N_u < 4\) |
no_fix |
| 2 | \(a_c\) not available or more than rtk_max_age_s, or \(N_u\) less than float_min_satellites |
dgps when \(a_c \le\) dgps_max_age_s, if not single |
| 3 | All other epochs | rtk_float or rtk_fixed |
States 1 and 2 end the RTK lock and record the time of the loss \(t_{loss}\). In state 3, the model does these steps.
- With no lock, start a lock. Set the lock time \(t_{lock} = t\) and the convergence time \(c = 0\). Draw a time to fix.
- With a lock, a cycle slip occurs when the uniform value is less than
cycle_slip_rate_per_s\(\cdot\, m\, \Delta t\). Then set \(t_{loss} = t\), \(c = 0\) and draw a new time to fix. - The epoch is capable when \(N_c \ge\)
min_clear_satellitesand \(P \le\)max_pdop. A capable epoch adds \(\Delta t\) to \(c\). - The state is
rtk_fixedwhen the epoch is capable and \(c \ge T_{fix}\). If not, the state isrtk_float. - A change from
rtk_fixedtortk_floatsets \(t_{loss} = t\) and \(c = 0\) and draws a new time to fix.
The time to fix uses the Gaussian value \(g_t\) of the epoch.
\(T_{med}\) is ttf_median_s, \(\sigma_T\) is ttf_log_sigma, \(G_m\) is ttf_multipath_gain, \(r_q\) is reacquire_factor and \(T_q\) is reacquire_window_s.
A cycle slip does not change \(t_{lock}\). Thus the float filter continues and only the ambiguity convergence starts again.
With the start mode converged, the lock is 600 s old at \(t = 0\) and \(T_{fix} = 0\). The first capable epoch is then rtk_fixed.
Error Model#
Each fix state has its own parameters (states). The state no_fix uses the parameters of single.
The base standard deviations are \(s_H\) (sigma_h_m) and \(s_V\) (sigma_v_m). In the state rtk_float they decrease from the initial values.
\(\sigma_{h0}\) is initial_sigma_h_m and \(\tau_c\) is convergence_tau_s. The vertical value uses the same equation.
In the two RTK states, the baseline \(B\) (baseline_km) adds a term.
The DOP ratios scale the errors with the geometry.
The standard deviations of the three terms for the axes east, north and up are as follows.
\(w_H\), \(w_V\) are white_h_m, white_v_m, \(\mu_H\) is multipath_h_m and \(r_V\) is multipath_vertical_ratio.
The two correlated terms are first-order Gauss-Markov processes with unit variance.
\(\tau\) is tau_s of the current fix state and \(\tau_m\) is multipath_tau_s. In the first epoch, \(\phi = \phi_m = 0\) and \(\Delta t = 0.1\) s.
The error and the reported standard deviation of each axis are as follows.
The reported \(\sigma\) is thus the true standard deviation of the model. The covariance in the row is diagonal.
The 11 values of an epoch have this sequence: 3 for \(u\), 3 for the white term, 3 for \(z\), the uniform value, then \(g_t\).
With -SensorNoNoise, all three standard deviations are zero.
Datum Chain#
Geodetic converts the ENU output position \((e, n, u)\) to geodetic coordinates in six steps.
- Grid coordinates in US survey feet: \(E_g = E_0 + e / c_f\) and \(N_g = N_0 + n / c_f\), with \(c_f = 1200/3937\) m.
- NAD83(HARN) latitude and longitude from the inverse projection EPSG:2968.
- NAD83(2011) latitude and longitude: add the constant shift
harn_to_nad83_2011_shift_deg. - Heights: \(H_{navd} = c_f Z_0 + u\), \(N_g = N_0^g + a_e\, e + a_n\, n\) and \(h_{2011} = H_{navd} + N_g\).
- ECEF on the GRS80 ellipsoid, then the inverse of the Helmert transformation at the epoch of the observation.
- Geodetic coordinates on the WGS84 ellipsoid. The frame is WGS84 (G2139), which the model sets equal to ITRF2014.
\((E_0, N_0, Z_0)\) is origin_source_ft of Acres/Content/Simulation/ACRE/site.json: 2 977 500 ft, 1 902 500 ft and 705 ft.
\(N_0^g\), \(a_e\), \(a_n\) are n0_m, dn_de, dn_dn of the geoid plane.
Inverse projection. IndianaWestFeetToGeographic uses the series of Snyder (1987) on GRS80.
The constants are \(\varphi_0 = 37.5°\), \(\lambda_0 = -87.08333°\), \(k_0 = 0.999966667\), false easting \(x_0 = 900\,000\) m and false northing \(y_0 = 250\,000\) m.
With \(x = c_f E_g\) and \(y = c_f N_g\):
\(e^2 = 2f - f^2\) and \(e'^2 = e^2 / (1 - e^2)\), with \(a = 6\,378\,137\) m and \(1/f = 298.257222101\).
ECEF. GeodeticToEcef and EcefToGeodetic use these equations. The inverse does 8 iterations of the latitude.
The first value of the iteration is \(\varphi = \mathrm{atan2}(Z, p (1 - e^2))\). The WGS84 ellipsoid has \(1/f = 298.257223563\).
Helmert transformation. The parameters are those of "ITRF2014 to NAD83(2011) (1)" in the coordinate-frame convention. With the epoch \(t_y\) in decimal years and \(\delta = t_y - 2010.0\):
HelmertForward and HelmertInverse are these two equations. The epoch is start_utc plus \(t\), as a decimal year of the correct year length (DecimalYear).
The key output_frame selects the values of latitude_deg, longitude_deg and ellipsoidal_height_m.
output_frame |
Latitude and Longitude | Height |
|---|---|---|
wgs84_g2139 |
WGS84 (G2139) at the epoch | WGS84 ellipsoidal height |
nad83 |
NAD83(HARN) | NAD83(2011) ellipsoidal height |
A row also contains the values of both frames with the prefixes nad83_harn_, nad83_2011_ and wgs84_.
EnuFromWgs84 does the chain in the opposite direction.
Output Row#
A row of gnss.jsonl contains these groups of keys.
| Keys | Content |
|---|---|
fix_type, gga_quality, solution |
The fix state and its NMEA quality: 4 rtk_fixed, 5 rtk_float, 2 dgps, 1 single, 0 no_fix. |
satellites_used, satellites_clear, satellites_near_obstruction, satellites_foliage, satellites_blocked, satellites_above_mask |
The satellite counts. |
hdop, vdop, pdop, hdop_open_sky, vdop_open_sky |
The DOP values. |
multipath_index, reflector_index |
\(m\) and \(\rho\). |
correction_age_s, rtk_lock_age_s, rtk_converge_s, rtk_time_to_fix_s |
\(a_c\), \(t - t_{lock}\), \(c\) and \(T_{fix}\). |
model |
The standard deviations of the three terms, the correlation times, the DOP ratios and \(\Delta t\). |
position_enu_m, sigma_enu_m, covariance_enu_m2, position_std_m |
The position with its error, \(\sigma\), \(\sigma^2\) and the horizontal standard deviation. |
latitude_deg, longitude_deg, ellipsoidal_height_m, altitude_navd88_m, geoid_undulation_nad83_m |
The geodetic output. |
nmea_gga |
The sentence $GNGGA with its checksum. |
In the state no_fix, the position keys are null. Without site.json, a row has no geodetic keys and no sentence.
The sentence contains the UTC time, the latitude and longitude in degrees and minutes with 7 decimals, the quality and the satellite count.
It then contains HDOP, the NAVD88 altitude, the geoid separation, the correction age and the station 0001.
The geoid separation is the ellipsoidal height of the output frame minus the NAVD88 altitude.
The checksum is the exclusive OR of the characters between $ and *.
IMU#
The source is FAcresSensorRecorder::PhysicsSample in AcresSensors.cpp. The IMU point is at the lever arm \(\mathbf{r}_i\) (imu.position_flu_m).
The velocity of the IMU point uses the centre of mass \(\mathbf{p}_c\) of the chassis.
The acceleration is the finite difference of this velocity across one physics step. The specific force subtracts the gravity vector.
A stationary IMU thus reads +9.80665 m/s² on the up axis. The output is the mean of two consecutive steps, in FLU axes, with a bias and noise.
\(\sigma_a\) is accel_std_mps2 and \(\sigma_g\) is gyro_std_radps. \(\mathbf{g}_a\) and \(\mathbf{g}_g\) are three Gaussian values each, new for each sample.
The mean of two samples is a filter with a zero at 60 Hz.
It removes the 60 Hz velocity change of ±0.02 m/s that the brake solver gives to a parked vehicle.
The biases are constant in an episode. Start draws them one time.
\(\sigma_{ba}\) is accel_bias_mps2 and \(\sigma_{bg}\) is gyro_bias_radps. The file episode.json records the two vectors in imu_bias_flu.
The first row is at the third physics step, because the average needs two differences.
A reset of the vehicle starts the differences again. Thus the jump of the pose does not appear as an acceleration.
A row of imu.jsonl contains specific_force_flu_mps2 with 6 decimals and angular_velocity_flu_radps with 8 decimals.
INS#
The source is FAcresSensorRecorder::InsEpoch. The INS reports one point of the vehicle at the rate ins.hz.
The Polaris has the INS in its sensor overlay: an OxTS AV200 that reports base_footprint at 100 Hz.
The Maxxum gets the same INS at base_footprint when a sensor stream is open, with the defaults of the code.
Truth at the INS Point#
Errors#
Six unit Gauss-Markov states \(x_1 \dots x_6\) give the errors of east, north, up, heading, roll and pitch.
\(\tau_p\) is position_tau_s. In the first epoch, \(x_j = g_j\) and \(\Delta t = 1/f\). The position output is as follows.
\(\sigma_p\) is position_sigma_m and \(\sigma_{pw}\) is position_white_m. The attitude errors are small rotations.
\(\sigma_\psi\) is heading_sigma_deg and \(\sigma_\theta\) is attitude_sigma_deg. \(\mathbf{q}_{true}\) is the rotation from FLU to ENU.
The heading error turns about the ENU up axis. The roll and pitch errors turn about the body axes.
The velocity, the angular rate and the specific force are in FLU axes with white noise.
\(\sigma_v\), \(\sigma_f\), \(\sigma_\omega\) are velocity_white_mps, accel_white_mps2 and gyro_white_radps.
Here \(\Delta t\) is the time since the previous INS epoch. The first epoch and the first epoch after a reset report gravity only.
The 18 values of an epoch have this sequence: 6 for the states and 3 for the position noise.
Then 3 for the velocity, 3 for the specific force and 3 for the angular rate.
Fix and UTM Odometry#
The datum chain of the GNSS receiver converts \(\mathbf{p}_{ins}\) to latitude, longitude and height. The key ins.frame selects the frame.
ins.frame |
Latitude and Longitude | Height |
|---|---|---|
nad83 |
NAD83(2011) | NAD83(2011) ellipsoidal height |
wgs84_g2139 |
WGS84 (G2139) at the epoch | WGS84 ellipsoidal height |
GeographicToUtm projects the latitude and longitude to the zone ins.utm_zone. The orientation of the odometry is in the UTM grid.
The model finds the grid rotation \(\alpha\) from a second point that is 10 m to the grid north of the first point.
The model computes \(\alpha\) again in each epoch. At the origin of the tile \(\alpha\) is 0.054°. The fix has the status 2 (RTK) and a diagonal covariance.
Outputs#
A row of ins.jsonl has the frame name oxts_link and these objects.
| Object | Content |
|---|---|
fix |
Latitude, longitude, altitude, datum, status 2, service 1, the diagonal of the covariance. |
imu |
orientation_enu_xyzw, angular_velocity_flu_radps, linear_acceleration_flu_mps2. |
velocity |
linear_flu_mps, angular_flu_radps. |
position_enu_m |
\(\mathbf{p}_{ins}\) in the grid ENU frame. |
odom |
Frame utm, child frame base_footprint, the UTM position, \(\mathbf{q}_{utm}\), the twist, grid_rotation_deg. |
The sensor stream sends the same epoch as 40 values of 64 bits, with the truth of the point for the ROS 2 bridge.
The function ins_to_messages of the ROS 2 bridge converts an epoch to these messages.
| Topic, Polaris | Topic, Maxxum | Message | Content |
|---|---|---|---|
vehicle/odom |
odom |
nav_msgs/Odometry |
UTM pose with \(\mathbf{q}_{utm}\), covariance diagonal, FLU twist. Frame utm. |
oxts/fix |
gnss/fix |
sensor_msgs/NavSatFix |
The fix. Frame navsat_link or gnss_link. |
oxts/imu |
imu/data |
sensor_msgs/Imu |
Attitude, angular rate and specific force. |
oxts/velocity |
- | geometry_msgs/TwistStamped |
FLU velocity in m/s and angular rate in degrees per second. Frame oxts_link. |
sim/ground_truth/odom |
ground_truth/odom |
nav_msgs/Odometry |
The pose without errors in the same UTM frame. |
The bridge also sends the transform from utm to base_footprint.
The IMU message of the Polaris uses the mounting axes of the OxTS unit: x left, y forward, z down.
The IMU message of the Maxxum stays in the body FLU axes. The Topics page lists all topics.
UTM Projection#
The source is AcresGeoUtm.h. It is a header without Unreal types, and the game and ACRES Core use the same file.
The method is the series of Krüger to the sixth order in the third flattening \(n\) (Karney 2011).
The ellipsoid is WGS84, the scale on the central meridian is \(k_0 = 0.9996\) and the false easting is \(E_0 = 500\,000\) m.
The functions are for the northern hemisphere. The central meridian of the zone \(z\) is \(\lambda_c = -183° + 6° z\). Zone 16 has \(\lambda_c = -87°\).
Forward (GeographicToUtm). With \(\tau = \tan\varphi\) and \(\lambda\) measured from \(\lambda_c\):
The coefficients are as follows.
Convergence and scale. With \(p = 1 + \sum 2 j \alpha_j \cos(2 j \xi') \cosh(2 j \eta')\) and \(q = \sum 2 j \alpha_j \sin(2 j \xi') \sinh(2 j \eta')\):
\(\gamma\) is grid north minus true north. It is positive to the east of the central meridian.
Inverse (UtmToGeographic). With \(\xi = N / (k_0 A)\) and \(\eta = (E - E_0) / (k_0 A)\):
Newton iterations find \(\tau\) from \(\tau'\). The first value is \(\tau = \tau'\). The loop stops after 6 iterations or at a step below \(10^{-14}\).
\(\tau'_i\) is \(\tau'\) of the current \(\tau\).
The table gives the values at the origin of the tile for the NAD83(2011) coordinates of the datum chain.
| Quantity | Value |
|---|---|
| Latitude, longitude | 40.4715765° N, 86.9943737° W |
| Easting, northing, zone 16 | 500 476.935 m, 4 480 099.755 m |
| Meridian convergence \(\gamma\) | 0.00365° |
| Point scale factor \(k\) | 0.99960000 |
| Grid rotation \(\alpha\) | 0.0541° |
An independent implementation and pyproj give the same easting and northing to less than 0.001 mm.
Georeference Caveats#
The georeference of the simulator has these known limits.
- Datum of a fix. A fix of the real vehicle does not state its datum. An RTK fix from the Indiana network is in NAD83(2011). A fix without corrections is in WGS84 (G2139) at the epoch of the observation. The two frames differ by 1.30 m on the tile at the epoch 2026.73: 0.97 m to the west and 0.87 m to the north. The WGS84 height is 1.13 m lower. Thus a tool that converts a fix to the grid must know the datum of the fix.
- Different defaults. The INS uses
nad83and the GNSS receiver useswgs84_g2139. The two outputs thus differ by 1.30 m. - Two meanings of
nad83. The GNSS receiver gives NAD83(HARN) latitude and longitude. The INS gives NAD83(2011). The difference is 2.1 cm. - Ellipsoid of the UTM projection. The projection uses the WGS84 ellipsoid for NAD83(2011) coordinates also, as the converter of the vehicle does.
- False northing. The projection EPSG:2968 in the code uses 250 000 m. The EPSG value is 0.1 mm smaller.
- Geoid. The geoid is a plane fit to the GEOID12B grid. The residual of the fit is less than 0.3 mm on the tile.
- Landmark.
episode.jsonreports one landmark that the project supplied. No survey confirmed this landmark, and the model does not use it.
The Python module Learning/acres_learn/deploy/georef.py does the same chain from UTM to the grid for the real vehicle.
ACRES Core INS#
Of these three sensors, ACRES Core has the INS only. The source is FInsModel in ROS/acres_core_sim (CoreSimIns.h), with the georeference in CoreSimGeo.h.
The node acres_core_sim sends the same 40 values on its sensor stream.
| Item | Game | ACRES Core |
|---|---|---|
| Error model, sequence of the 18 values | InsEpoch |
The same equations in FInsModel::Epoch |
| Generator | FAcresPcg32 |
FPcg32, the same algorithm |
| Seed of the stream | seed + 5045, sequence 6 |
seed + 5045 + 7919 × agent index, sequence 6 |
| Configuration | sensors.json with the vehicle overlay |
The block ins of sensors_polaris.json and the seed of sensors.json |
| Sample times | First epoch at \(t = 0\) of the recording | First epoch at the first sample |
| Frames | nad83 and wgs84_g2139 |
nad83 only |
| Datum chain | FAcresGnssModel::Geodetic |
FSiteGeoreference::EnuToNad83, steps 1 to 4 of the chain |
| UTM projection | AcresGeoUtm.h |
The same header |
| Noise off | -SensorNoNoise |
--no-noise, or noise:=false of the launch file |
| GNSS receiver and IMU models | Yes | No |
Agent 0 of ACRES Core thus uses the same stream of values as the game.
FSiteGeoreference also gives an affine map between the grid and UTM for the simulator control channel.
The map uses the UTM position of the origin and one grid rotation. It is correct to a few millimetres across the tile.
Parameters#
The defaults are the values of Acres/Content/Simulation/sensors.json. The Polaris column is the value after the overlay sensors_polaris.json.
Common Keys#
| Name | Type | Unit | Default | Description |
|---|---|---|---|---|
seed |
integer | 42 | The seed of all noise streams. | |
delay_s |
number | s | 0.05 | The modelled latency of the GNSS, IMU and INS rows. Range 0 to 10. |
mount_frame |
string | chassis |
The origin of the mounts: chassis or base_footprint. The Polaris overlay sets base_footprint. |
GNSS Receiver#
| Name | Type | Unit | Default | Description |
|---|---|---|---|---|
gnss.enabled |
boolean | true | The receiver is on. | |
gnss.hz |
number | Hz | 10 | The epoch rate. Range 1 to 120. |
gnss.antenna_flu_m |
array of 3 | m | [0, 0, 1.7] | The lever arm of the antenna. Polaris: [1.1, 0, 2.15]. |
gnss.std_m |
number | m | 0.02 | Not used. The error model sets the noise. |
gnss.start |
string | converged |
converged or cold. |
|
gnss.cold_ttff_s |
number | s | 20 | The time to the first fix after a cold start. |
gnss.elevation_mask_deg |
number | deg | 10 | The elevation mask. Range 0 to 45. |
gnss.constellations.gps, .galileo, .glonass |
boolean | true | The systems that the receiver uses. One system must be on. | |
gnss.multipath_tau_s |
number | s | 8 | The correlation time of the multipath error. |
gnss.multipath_vertical_ratio |
number | 1.5 | The vertical multipath error divided by the horizontal error. |
The block gnss.states has one object for each fix state.
| Name | Type | Unit | rtk_fixed |
rtk_float |
dgps |
single |
|---|---|---|---|---|---|---|
sigma_h_m |
number | m | 0.008 | 0.10 | 0.35 | 1.0 |
sigma_v_m |
number | m | 0.015 | 0.20 | 0.70 | 2.0 |
initial_sigma_h_m |
number | m | 0 | 0.40 | 0 | 0 |
initial_sigma_v_m |
number | m | 0 | 0.80 | 0 | 0 |
convergence_tau_s |
number | s | 30 | 30 | 30 | 30 |
white_h_m |
number | m | 0.003 | 0.02 | 0.10 | 0.30 |
white_v_m |
number | m | 0.006 | 0.04 | 0.20 | 0.60 |
tau_s |
number | s | 60 | 30 | 120 | 300 |
multipath_h_m |
number | m | 0.015 | 0.15 | 0.50 | 1.50 |
| Name | Type | Unit | Default | Description |
|---|---|---|---|---|
gnss.rtk.ttf_median_s |
number | s | 10 | The median time to fix. |
gnss.rtk.ttf_log_sigma |
number | 0.5 | The standard deviation of the logarithm of the time to fix. | |
gnss.rtk.ttf_multipath_gain |
number | 2 | The increase of the time to fix at the multipath index 1. | |
gnss.rtk.reacquire_factor |
number | 0.3 | The factor of the time to fix after a recent loss. Range 0 to 1. | |
gnss.rtk.reacquire_window_s |
number | s | 10 | The time after a loss in which the factor applies. |
gnss.rtk.min_clear_satellites |
integer | 6 | The clear satellites that the convergence needs. Minimum 4. | |
gnss.rtk.float_min_satellites |
integer | 5 | The used satellites that an RTK solution needs. Minimum 4. | |
gnss.rtk.max_pdop |
number | 5 | The PDOP limit of the convergence. | |
gnss.rtk.cycle_slip_rate_per_s |
number | 1/s | 0.3 | The cycle slip rate at the multipath index 1. |
gnss.corrections.interval_s |
number | s | 1 | The interval of the correction messages. |
gnss.corrections.latency_s |
number | s | 0.3 | The delay of a correction message. |
gnss.corrections.rtk_max_age_s |
number | s | 10 | The maximum correction age for RTK. |
gnss.corrections.dgps_max_age_s |
number | s | 60 | The maximum correction age for DGPS. |
gnss.corrections.baseline_km |
number | km | 5 | The distance to the base station. Range 0 to 200. |
gnss.corrections.ppm_h, ppm_v |
number | ppm | 1, 1.5 | The error for each kilometre of baseline, in parts per million. |
gnss.corrections.outages_s |
array | s | [] | Correction outages as pairs [start, end). |
gnss.obstruction.near_margin_m |
number | m | 1.5 | The margin that makes a line of sight "near". |
gnss.obstruction.foliage_block_path_m |
number | m | 8 | The path in a tree crown that blocks a satellite. |
gnss.obstruction.reflector_range_m |
number | m | 60 | The range of the reflector index. |
gnss.datum.output_frame |
string | wgs84_g2139 |
wgs84_g2139 or nad83. |
|
gnss.datum.start_utc |
string | 2026-09-23T15:00:00Z |
The UTC time at \(t = 0\), in the ISO 8601 format. | |
gnss.datum.harn_to_nad83_2011_shift_deg |
array of 2 | deg | [-7.742258e-09, 2.4261007e-07] | The shift of latitude and longitude from NAD83(HARN) to NAD83(2011). |
gnss.datum.geoid.n0_m |
number | m | -33.6268735 | The geoid undulation at the origin. |
gnss.datum.geoid.dn_de, dn_dn |
number | m/m | -1.07779725e-05, -6.19783884e-06 | The slope of the geoid plane to the east and to the north. |
The block gnss.datum.helmert_itrf2014_to_nad83_2011 has the 14 parameters of the Helmert transformation.
| Name | Type | Unit | Default | Description |
|---|---|---|---|---|
t_m |
array of 3 | m | [1.00530, -1.90921, -0.54157] | The translation \(\mathbf{T}_0\). |
r_arcsec |
array of 3 | arcsec | [0.02678138, -0.00042027, 0.01093206] | The rotation \(\mathbf{r}_0\). |
s_ppm |
number | ppm | 0.00036891 | The scale \(s_0\). |
dt_m_per_yr |
array of 3 | m/yr | [0.00079, -0.00060, -0.00144] | The rate of the translation. |
dr_arcsec_per_yr |
array of 3 | arcsec/yr | [0.00006667, -0.00075744, -0.00005133] | The rate of the rotation. |
ds_ppm_per_yr |
number | ppm/yr | -0.00007201 | The rate of the scale. |
reference_epoch |
number | yr | 2010.0 | The reference epoch. |
IMU#
| Name | Type | Unit | Default | Description |
|---|---|---|---|---|
imu.enabled |
boolean | true | The IMU is on. | |
imu.hz |
number | Hz | 120 | The sample rate. Range 1 to 120. |
imu.accel_std_mps2 |
number | m/s² | 0.03 | The white noise of the specific force, each axis and sample. |
imu.gyro_std_radps |
number | rad/s | 0.002 | The white noise of the angular rate, each axis and sample. |
imu.accel_bias_mps2 |
number | m/s² | 0.01 | The standard deviation of the constant accelerometer bias. |
imu.gyro_bias_radps |
number | rad/s | 0.001 | The standard deviation of the constant gyroscope bias. |
imu.position_flu_m |
array of 3 | m | [0, 0, 0] | The lever arm of the IMU. Polaris: [0.95, -0.45, 0.7]. |
INS#
The block ins is in sensors_polaris.json only. The column "Code" gives the default without the block.
| Name | Type | Unit | Default | Code | Description |
|---|---|---|---|---|---|
ins.enabled |
boolean | true | false | The INS is on. An open sensor stream sets it on. | |
ins.hz |
number | Hz | 100 | 100 | The epoch rate. Range 1 to 120. |
ins.position_flu_m |
array of 3 | m | [0, 0, 0] | [0, 0, 0] | The INS point. The Polaris value is base_footprint. |
ins.position_sigma_m |
number | m | 0.01 | 0.01 | The Gauss-Markov position error. The up axis uses 1.5 times this value. |
ins.position_tau_s |
number | s | 30 | 30 | The correlation time of the six states. |
ins.position_white_m |
number | m | 0.003 | 0.003 | The white position noise. |
ins.heading_sigma_deg |
number | deg | 0.1 | 0.1 | The heading error. |
ins.attitude_sigma_deg |
number | deg | 0.03 | 0.03 | The roll error and the pitch error. |
ins.velocity_white_mps |
number | m/s | 0.02 | 0.01 | The white velocity noise. |
ins.accel_white_mps2 |
number | m/s² | 0.02 | 0.02 | The white noise of the specific force. |
ins.gyro_white_radps |
number | rad/s | 0.0005 | 0.0005 | The white noise of the angular rate. |
ins.frame |
string | nad83 |
nad83 |
nad83 or wgs84_g2139. |
|
ins.utm_zone |
integer | 16 | 16 | The UTM zone of the odometry. Range 1 to 60. |
Command-Line Options#
| Name | Type | Unit | Default | Description |
|---|---|---|---|---|
-SensorSeed= |
integer | seed |
Replaces the seed of all noise streams. | |
-SensorDelay= |
number | s | delay_s |
Replaces the modelled latency. |
-SensorGnssHz= |
number | Hz | gnss.hz |
Replaces the GNSS rate. |
-SensorImuHz= |
number | Hz | imu.hz |
Replaces the IMU rate. |
-SensorNoGnss, -SensorNoImu, -SensorNoIns |
flag | Sets one sensor off. | ||
-SensorNoNoise |
flag | Sets all errors, biases and noise of all sensors to zero. | ||
-SensorGnssStart= |
string | gnss.start |
converged or cold. |
|
-SensorGnssFrame= |
string | gnss.datum.output_frame |
wgs84_g2139 or nad83. |
|
-SensorGnssBaselineKm= |
number | km | gnss.corrections.baseline_km |
Replaces the baseline. |
-SensorGnssOutage= |
string | s | Adds one correction outage as <start>:<end>. |
|
-SensorConfig= |
path | sensors.json |
Uses a different complete configuration file. | |
-PolarisSensorConfig= |
path | sensors_polaris.json |
Uses a different overlay for the Polaris. |
The INS has no rate option. Change ins.hz in the overlay.
The game stops with a message when a value is out of its range.
The Sensors and Rigs tutorial shows how to use these keys and options.
Code Map#
| Item | File | Function |
|---|---|---|
| Receiver epoch, state machine, error model, row | Acres/Source/Acres/AcresGnssModel.cpp |
FAcresGnssModel::Epoch |
| Constellation | AcresGnssModel.cpp |
FAcresGnssModel::Initialize |
| Obstacles | AcresGnssModel.cpp, AcresSensors.cpp |
GatherStructures, FAcresSensorRecorder::Start |
| Ray tests, DOP | AcresGnssModel.cpp |
RayBox, RayCylinder, SphereChord, Dop |
| Correction age | AcresGnssModel.cpp |
FAcresGnssModel::CorrectionAge |
| Datum chain | AcresGnssModel.cpp |
Geodetic, EnuFromWgs84, HelmertForward, HelmertInverse, GeodeticToEcef, EcefToGeodetic |
| Projection EPSG:2968 | AcresSensors.cpp |
IndianaWestFeetToGeographic, GeographicToIndianaWestFeet |
| UTM projection | Acres/Source/Acres/AcresGeoUtm.h |
GeographicToUtm, UtmToGeographic |
| GNSS configuration | AcresGnssModel.cpp |
FAcresGnssModelConfig::Parse, ApplyCommandLine, Validate |
| IMU | AcresSensors.cpp |
FAcresSensorRecorder::PhysicsSample |
| INS | AcresSensors.cpp |
FAcresSensorRecorder::InsEpoch |
| Sample times, latency | AcresSensors.cpp |
Due, Emit, Drain |
| Generator | AcresSensors.cpp |
FAcresPcg32::Seed, Next, Gaussian |
| Configuration and options | AcresSensors.cpp |
LoadSensorConfig, FAcresSensorConfig::Load |
| INS to ROS 2 messages | ROS/acres_sim/src/conversions.cpp |
ins_to_messages |
| ACRES Core INS | ROS/acres_core_sim/src/CoreSimIns.cpp |
FInsModel::Epoch, FInsConfig::Load |
| ACRES Core georeference | ROS/acres_core_sim/src/CoreSimGeo.cpp |
FSiteGeoreference::EnuToNad83, EnuToUtm, WorldToUtm |
| Tests | ROS/acres_core_sim/test/test_core_sim_unit.cpp, Tools/PolarisModel/polaris_tests.cpp |
Geo, Ins and UTM tests |
Limitations#
- The parameters of the GNSS receiver and of the IMU are typical datasheet values. They are not a calibration of a real receiver.
- The GNSS receiver has no ionosphere, no troposphere, no receiver clock and no velocity output.
- The multipath index is a heuristic. The obstacles are boxes, cylinders and spheres.
- At the default spawn, the antenna of a vehicle is in the box of the ICSC garage. The receiver has no fix until the vehicle moves away from the garage.
- The IMU reads the velocity of the physics solver. A parked vehicle shows the small velocity changes of the brake solver in the truth rows.
- The INS does not use the GNSS receiver model. Its fix status is always 2, also below obstacles.
- The lever arms of the Polaris INS are estimates. The project has no record of the real values of the OxTS configuration.
- The grid ENU frame is not a tangent frame. The grid north differs from the true north by 0.058° at the origin.
References#
- Karney, C. F. F. (2011). Transverse Mercator with an accuracy of a few nanometers. Journal of Geodesy, 85(8), 475-485.
- Snyder, J. P. (1987). Map Projections: A Working Manual. U.S. Geological Survey Professional Paper 1395.
- O'Neill, M. E. (2014). PCG: A Family of Simple Fast Space-Efficient Statistically Good Algorithms for Random Number Generation. Technical report HMC-CS-2014-0905, Harvey Mudd College.
- Box, G. E. P., and Muller, M. E. (1958). A note on the generation of random normal deviates. The Annals of Mathematical Statistics, 29(2), 610-611.
- National Geodetic Survey. HTDP: Horizontal Time-Dependent Positioning, transformation "ITRF2014 to NAD83(2011) (1)" of the EPSG registry.
- National Geodetic Survey. GEOID12B and NADCON5 grids.
- National Marine Electronics Association. NMEA 0183 Standard for Interfacing Marine Electronic Devices, sentence GGA.