SP3-c / SP3-d precise-ephemeris products (IGS precise orbits + clocks).
This is the Elixir surface over the sidereon-core SP3 parser and
scipy.interpolate-matched position/clock interpolation. It is not the
JPL-SPK reader (Sidereon.Ephemeris): SP3 carries GNSS satellite states in the
ITRF/IGS ECEF frame, in meters, tagged by a GNSS satellite id like "G01".
A file is parsed once into a resource handle held by the BEAM; evaluation operates on that handle and never re-reads the file.
Example
{:ok, sp3} = Sidereon.GNSS.SP3.load("/path/to/igs.sp3")
{:ok, state} =
Sidereon.GNSS.SP3.position(sp3, "G01", ~N[2020-06-24 00:00:00])
state.x_m # ITRF/IGS ECEF X, meters
state.clock_s # satellite clock offset, seconds (or nil if no estimate)Epochs
The query epoch is interpreted in the file's own time scale (read from the
SP3 header, typically GPST). Pass a NaiveDateTime or a
{{year, month, day}, {hour, minute, second}} tuple; it is converted to the
split Julian date with the same midnight-boundary convention the parser uses
(no leap-second shifting; the epoch stays in the file's scale).
Summary
Types
Exact declared-span representation found by the core validator.
Typed refusal for a numeric SP3 interpolation-policy input.
Typed refusal returned by to_iodata/2 and to_sp3_string/2.
The final {atom(), map()} case covers additional already named variants.
Functions
Return a copy of other with its clocks shifted onto reference's clock datum
(the clock-datum primitive, applied).
Attest that this product is physically continuous, or report each violation.
Estimate the per-epoch reference-clock offset of other relative to
reference (the clock-datum primitive).
Evaluate the product-clock relativistic term for an exact queried state.
Decide whether recorded defects can influence an inclusive product-scale J2000-seconds evaluation window.
Return the product coverage interval.
Return the epoch count declared on SP3 header line 1.
Return the start epoch declared on SP3 header line 1.
Read SP3 position variance at independent exact state and selection queries.
Number of parsed epochs held by the SP3 product.
Return the parsed SP3 epoch grid as seconds since J2000.
Return the position-interpolation gap threshold factor carried by the product.
Interpolate one satellite at J2000-second epochs in the product time scale.
Load and parse an SP3-c / SP3-d file into a product handle.
Like load/2 but raises on failure.
Merge several SP3 products from different analysis centers into one consistent precise-ephemeris dataset.
Epochs of the merged product's position nodes that some interpolation of
satellite in the inclusive window selects, ascending and seconds since
J2000: the nodes merge_continuity_verdict/3 reads for that window.
Decide whether an opt-in merge continuity report influences an inclusive evaluation window on the merged product's J2000-seconds axis.
Build the versioned stable identity for exact SP3 artifacts and merge policy.
Parse an in-memory SP3 byte buffer (already decompressed) into a handle.
Parse and validate decompressed SP3 bytes against an exact request.
Interpolate the state of satellite sat_id at epoch.
Interpolate an SP3 state at an exact query without converting it to a civil float.
Extract variance sidecars aligned by satellite and epoch with precise samples.
Extract the product as the canonical precise-ephemeris samples, in SI units, one per real position record in ascending epoch order.
Build canonical precise-interpolant artifact bytes from this SP3 product.
Return observed/predicted status derived from the SP3 record flags.
Return decoded P/V standard deviations for one retained satellite record.
Return raw signed P/V accuracy codes and their source bases for one record.
Return the SP3/RINEX satellite identifiers declared by the product header.
Alias for satellite_ids/1, matching the Python/WASM satellites accessor.
Epochs of the position nodes that some interpolation of satellite in the
inclusive J2000-seconds window selects, ascending; [] when no query in the
window is served for it.
Read selected state and group delay at independent exact state and selection queries.
Return the exact parsed state of sat_id at epoch_index.
Return all exact parsed states at epoch_index.
Return the time reach of the product's position-interpolation stencil.
Serialize the product to standard SP3-c / SP3-d text as iodata. Pure, no I/O.
Serialize the product to an SP3 text binary.
Read the clock used for transmission placement at exact state and selection queries.
Validate an already parsed SP3 product against an exact request.
Types
@type exact_coverage() :: :half_open | :inclusive
Exact declared-span representation found by the core validator.
@type interpolation_error() :: %{ kind: String.t(), field: String.t(), value: String.t(), reason: String.t() }
Typed refusal for a numeric SP3 interpolation-policy input.
@type writer_error() :: {:satellite_not_representable, %{satellite: binary(), system: binary(), prn: 0..255}} | {:accuracy_not_representable, %{ satellite: binary(), epoch_index: non_neg_integer(), component: binary(), exponent: integer() | nil, message: binary() }} | {:accuracy_record_mismatch | :accuracy_basis_missing, %{satellite: binary(), epoch_index: non_neg_integer(), message: binary()}} | {atom(), map()}
Typed refusal returned by to_iodata/2 and to_sp3_string/2.
The final {atom(), map()} case covers additional already named variants.
Functions
Return a copy of other with its clocks shifted onto reference's clock datum
(the clock-datum primitive, applied).
At every epoch the offset could be estimated, each clocked satellite's offset
has the datum subtracted, so the result's clocks are directly comparable to
reference's. Positions are untouched. Epochs without an estimate are left
unchanged. The returned product interpolates like any other SP3.
Returns {:ok, %Sidereon.GNSS.SP3{}} or {:error, reason}.
Options
:min_common: minimum common clocked satellites per epoch (default5)
Attest that this product is physically continuous, or report each violation.
A merged product is assembled per satellite and epoch from several analysis centers, which is exactly the operation that can splice two physically inconsistent arcs together while every input stays individually well-formed. This runs two checks with different jobs:
a physical earth-fixed speed gate, whose bound is a true upper bound for the orbit class, so it cannot false-positive. It catches gross corruption (a record from the wrong satellite or the wrong day) and is insensitive by construction: adjacent GNSS MEO epochs are hundreds of kilometres apart, so a metre-scale splice moves the implied speed by a fraction of a percent.
a hold-out interpolation residual, which supplies the sensitivity. Each interior sample is predicted from its neighbours through the product's own interpolator and compared against the stored record, resolving a splice of a few metres.
Options:
:orbit_class-:meo_gnss(default),:geosynchronous,:leo, ornilto disable the speed gate.:residual_tolerance_m- tolerance for the residual check, default1.0;nildisables it.
Returns {:ok, report} where report has :defects, :attested?, and the
counts of what was examined, so "checked and clean" stays distinguishable from
"not checked". This reports rather than refuses: whether a product with
defects is acceptable is the caller's decision.
Each defect has :kind (:duplicate_epoch, :single_sample_series,
:unusable_sample, :speed_bound or :hold_out_residual), :satellite, the summary
:from_j2000_s, :to_j2000_s, :magnitude and :bound (nil where the
kind has none), and every field of its kind under the core's name:
:epoch_j2000_s and :occurrences; :interval_s, :displacement_m,
:implied_speed_m_s and :bound_m_s; or :epoch_j2000_s,
:preceding_j2000_s, :residual_m, :tolerance_m and :node_epochs_j2000_s.
An :unusable_sample also includes :sample_index, :epoch_j2000_s (when
placed), and a :reason of :epoch_not_placed or :non_finite_position.
Example
{:ok, report} = Sidereon.GNSS.SP3.check_continuity(sp3)
report.attested?
Estimate the per-epoch reference-clock offset of other relative to
reference (the clock-datum primitive).
Precise clock products from different centers are referenced to different
station/ensemble clocks, so their raw clocks differ by a per-epoch common
offset that drifts over the day. This returns that datum: a list of maps
%{jd_whole: float, jd_fraction: float, offset_s: float, satellites: integer},
one per epoch where at least :min_common common clocked satellites let the
(robust median) offset be estimated. Subtract offset_s from other's clocks
to put both products on reference's datum. Orbit positions need no such
treatment; every center reports ITRF center-of-mass coordinates.
Options
:min_common: minimum common clocked satellites per epoch (default5)
Evaluate the product-clock relativistic term for an exact queried state.
Decide whether recorded defects can influence an inclusive product-scale J2000-seconds evaluation window.
The existing product-wide checks and options are unchanged. The core filters
their report using the interpolation reach returned by stencil_extent/1,
retaining both the influencing subset and every product-wide finding.
Return the product coverage interval.
The start and end are the first and last SP3 node epochs, expressed as
seconds since J2000 in the product's own time scale. Public evaluators reject
epochs outside this interval by default; pass extrapolate: true to the
evaluator to opt into the lower-level interpolation behavior.
@spec declared_epoch_count(t()) :: non_neg_integer()
Return the epoch count declared on SP3 header line 1.
This can differ from epoch_count/1 for a truncated or inconsistent product
accepted by the compatibility parser. validate_exact/2 requires equality.
Alias for declared_start_j2000_seconds/1.
Return the start epoch declared on SP3 header line 1.
The value is seconds since J2000 in the product's own time scale, or nil
when the permissive parser could not interpret the declaration.
Read SP3 position variance at independent exact state and selection queries.
@spec epoch_count(t()) :: non_neg_integer()
Number of parsed epochs held by the SP3 product.
This is the count of actual * epoch nodes parsed from the file, not just the
header declaration. The value matches length(epochs_j2000_seconds(sp3)) for
ordinary SP3 products.
Return the parsed SP3 epoch grid as seconds since J2000.
Values are in the product's own time scale, ascending, and correspond exactly to the parsed SP3 node epochs. Use this accessor when a caller needs the original sample grid rather than an interpolated state.
Return the position-interpolation gap threshold factor carried by the product.
@spec interpolate(t(), String.t(), [number()]) :: {:ok, Sidereon.GNSS.PreciseEphemeris.StateBatch.t()} | {:error, term()}
Interpolate one satellite at J2000-second epochs in the product time scale.
This returns the same Sidereon.GNSS.PreciseEphemeris.StateBatch used by the
precise-interpolant accessors.
Load and parse an SP3-c / SP3-d file into a product handle.
Returns {:ok, %Sidereon.GNSS.SP3{}} or {:error, reason}. The file is read and
parsed exactly once; the parsed product is held as a resource handle.
Options:
:gap_threshold_factor- multiple of nominal node spacing above which consecutive records mark a coverage gap (default1.5, must be > 1.0).
A numeric factor at or below 1.0 returns {:error, interpolation_error()}
with the supplied value and core refusal reason.
Like load/2 but raises on failure.
Merge several SP3 products from different analysis centers into one consistent precise-ephemeris dataset.
sources is a list of loaded products in precedence order (earlier wins
ties). This is orthogonal to time-stitching: it combines providers at the same
epochs on one shared time grid. By default the grid step is the greatest
common divisor of the inputs' epoch steps and offsets, which holds every input
epoch (900 s and 400 s products merge on a 100 s grid); only records actually
present in an input are used, and nothing is interpolated. For every (epoch, satellite) cell:
- Union satellite coverage: a satellite present in any input may appear in the merged product wherever a source actually carries it.
- Consensus: the largest subset of sources agreeing within tolerance is
combined; sources outside it are recorded as outliers. A cell with no
agreeing subset of
:min_agreeis quarantined (omitted), never averaged across disagreeing centers. A lone source is carried through. - Cell precedence: with
combine: :precedence, the earliest source present in each cell wins, so a lower-precedence source fills a preferred source's missing cell. Setprecedence_scope: :satellite_arcto retain one owner for a whole satellite arc. - Optional precedence guard:
:outlier_rejectmakes a contested precedence cell require a mutually agreeing cluster of at leastmax(:min_agree, 2)sources. A corrupt preferred value is replaced by the earliest member of the deterministic largest cluster and recorded.
Returns {:ok, %Sidereon.GNSS.SP3{}, report} or {:error, reason}, where
report is a map with :quarantined, :single_source, and
:position_outliers, and :clock_outliers lists. Each entry is a map
%{satellite: "G03", jd_whole: float, jd_fraction: float, sources: [0, 2]}
(sources are zero-based indices into sources).
report.agreement quantifies how tightly the consensus sources clustered about
the combined product. It is a map with the whole-product aggregates
:position_rms_m, :position_max_m, :clock_rms_s, and :clock_max_s, plus
:cells (per-(epoch, satellite) statistics, one per accepted cell) and
:epochs (per-epoch aggregates). The whole-product RMS fields are nil when
no multi-source consensus exists for that channel; its maxima cover every
accepted position cell and every clock-bearing cell, so either may be 0.0
for single-source cells, and is nil when no accepted cell carries that
channel. A cell's position fields are nil when the cell
carries no position, such as a clock-only record, and its clock fields nil
when it carries no clock; neither is read as 0.0. An epoch's aggregates
cover its multi-source cells and are nil for a channel with none there.
The report also states what the merge did not write:
:dropped_input_epochs- input epochs that took no part, in (source, epoch) order, each%{source: 0, epoch_index: 3, epoch: epoch, reason: reason}withreason:off_target_grid(off an explicit:epoch_interval_sgrid) or:not_on_tick_axis(no SP3 epoch record states the instant exactly).:omitted_epochs- union-grid epochs at which the merge accepted no cell and wrote no epoch.:arc_withheld- cells whose position:precedencedid not write because the preferred source carried none there, each%{satellite: "G03", epoch: epoch, sources: [1]}.:clock_omissions- each source clock the merge did not write, in (epoch, satellite, source) order, each%{epoch: epoch, satellite: "G03", source: 1, reason: reason, preferred: nil | 0, cell_has_clock: true}withreason:datum_not_observable,:preferred_source_without_clock(with the preferred source under:preferredwhen the merge had one) or:no_consensus.
An epoch there is %{time_scale: "GPST", jd_whole: float, jd_fraction: float} or, for an integer-nanosecond instant,
%{time_scale: "GPST", nanos_since_j2000: integer}, as the merge recorded it.
report.continuity is nil unless :verify_continuity was set, and
otherwise the check_continuity/2 report of the merged product with
:violations and :splices (the violations that cross a contributor change).
Each violation has :defect, :from_sources, :to_sources, :sources,
:crosses_contributors and :cells, each cell %{epoch_j2000_s: float, role: role, selection: selection | nil} with role :held_out,
:interpolation_node, :pair_end or :repeated_epoch. A selection is
%{kind: :single_source, source: 0}, %{kind: :precedence, source: 0, members: [0, 1]} or %{kind: :combined, rule: :mean, members: [0, 1]}.
report.provenance is nil unless :provenance was set, and otherwise
%{mode: mode, cells: cells, transitions: transitions, coverage: coverage}:
:cells has one %{epoch: epoch, satellite: "G03", position: selection | nil, clock: selection | nil} per accepted cell under :full and none under
:summary; each transition is %{satellite: "G03", epoch: epoch, from_source: 0 | nil, to_source: 1 | nil, reason: reason} with reason
:sole_availability, :precedence, :outlier_rejection or
:consensus_change; each coverage entry is %{source: 0, cells_contributed: integer, cells_selected: integer, first_epoch: epoch | nil, last_epoch: epoch | nil, cells_absent: integer}.
Options
:position_tolerance_m: position agreement tolerance, meters (default0.5):clock_tolerance_s: clock agreement tolerance, seconds (default5.0e-9):min_agree: agreeing sources required to accept a contested cell (default2):clock_min_common: common clocked satellites for the clock-datum estimate (default5):combine::mean(default),:median, or:precedence:precedence_scope::cell(default) or:satellite_arc:outlier_reject:nil(default/current behavior), or a map/keyword list with:position_mand:clock_nstolerances:epoch_interval_s: require this target epoch interval, seconds: a whole number of the 10-nanosecond ticks an SP3 interval states. The grid is anchored at the earliest input epoch.:systems: restrict output to systems such as[:gps]or["G", "E"]:asserted_frame_label_sets: coordinate-label sets the caller asserts are equivalent without frame math:helmert: enable catalog Helmert reconciliation for known ITRF/IGS labels:verify_continuity:false(default),truefor the standard options, or continuity options with:orbit_classand:residual_tolerance_m:provenance:nil(default, none recorded),:summaryor:full
@spec merge_continuity_selected_nodes(map(), String.t(), number(), number()) :: {:ok, [float()] | nil} | {:error, term()}
Epochs of the merged product's position nodes that some interpolation of
satellite in the inclusive window selects, ascending and seconds since
J2000: the nodes merge_continuity_verdict/3 reads for that window.
Returns {:ok, nil} when merge continuity verification was not requested.
Decide whether an opt-in merge continuity report influences an inclusive evaluation window on the merged product's J2000-seconds axis.
The report holds the merged product's interpolation nodes, so a violation influences the window when the nodes its interpolations select include the violation's held-out, repeated or pair-end record or straddle a handover between its records.
Returns {:ok, nil} when merge continuity verification was not requested.
Build the versioned stable identity for exact SP3 artifacts and merge policy.
Contributor order and map order do not affect mean or median identity. With
combine: :precedence, source order is an effective policy control and is
therefore bound in order. Every contributor must carry complete
requested/resolved identity, distributor, product and archive digests and
lengths, official filename, and compression. Cache and HTTP observations are
intentionally excluded. The returned map includes core's complete canonical
:contributors and, for precedence combination, the ordered
:precedence_contributors.
The options are those of merge/2, including the same exact positive
10-nanosecond tick and under-100000-second interval validation. The returned
:merge_policy records every option, including :verify_continuity (nil
or %{orbit_class: ..., residual_tolerance_m: ..., gap_threshold_factor: ...}) and :provenance (nil, "summary" or "full"), which change
neither the merged product nor the stable identity.
Parse an in-memory SP3 byte buffer (already decompressed) into a handle.
Options:
:gap_threshold_factor- multiple of nominal node spacing above which consecutive records mark a coverage gap (default1.5, must be > 1.0).
A numeric factor at or below 1.0 returns
{:error, interpolation_error()} with the supplied factor and core refusal
reason. SP3 grammar failures keep their existing error term.
@spec parse_exact(binary(), Sidereon.GNSS.SP3.ExactRequest.t(), keyword()) :: {:ok, t(), exact_coverage()} | {:error, term()}
Parse and validate decompressed SP3 bytes against an exact request.
Returns the parsed product together with :half_open or :inclusive to
identify the accepted boundary representation. Parse, identity, cadence,
grid, and span failures are returned as exact-product integrity errors.
Options:
:gap_threshold_factor- multiple of nominal node spacing above which consecutive records mark a coverage gap (default1.5, must be > 1.0).
@spec position(t(), String.t(), NaiveDateTime.t() | tuple(), keyword()) :: {:ok, Sidereon.GNSS.SP3.State.t()} | {:error, term()}
Interpolate the state of satellite sat_id at epoch.
sat_id is the canonical SP3/RINEX token, e.g. "G01" (GPS PRN 1), "E12",
"C30". epoch is a NaiveDateTime or a
{{year, month, day}, {hour, minute, second}} tuple, interpreted in the
file's own time scale.
By default, epochs outside the parsed SP3 node coverage return
{:error, :outside_coverage}. Pass extrapolate: true to opt into the
lower-level interpolation behavior near the product edges.
Positions are interpolated from the eleven nodes RTKLIB pephpos selects; a
query whose contiguous run holds fewer returns
{:error, {:insufficient_precise_nodes, sat_id, nodes, 11}}.
Returns {:ok, %Sidereon.GNSS.SP3.State{}} or {:error, reason}.
@spec position_at_epoch_query(t(), String.t(), Sidereon.GNSS.Time.ExactEpochQuery.t()) :: {:ok, Sidereon.GNSS.SP3.State.t()} | {:error, term()}
Interpolate an SP3 state at an exact query without converting it to a civil float.
@spec precise_ephemeris_accuracy_samples(t()) :: [ Sidereon.GNSS.PreciseEphemerisAccuracySample.t() ]
Extract variance sidecars aligned by satellite and epoch with precise samples.
@spec precise_ephemeris_samples(t()) :: [Sidereon.GNSS.PreciseEphemerisSample.t()]
Extract the product as the canonical precise-ephemeris samples, in SI units, one per real position record in ascending epoch order.
Each element is a Sidereon.GNSS.PreciseEphemerisSample carrying the satellite
token, the epoch (split Julian date tagged with the product's time scale), the
ECEF position in meters, the optional clock in seconds, and the SP3 E
clock-event flag. Round-tripping these back through
Sidereon.GNSS.PreciseEphemeris.from_samples/1 rebuilds the same interpolatable
source.
Examples
{:ok, sp3} = Sidereon.GNSS.SP3.load("igs.sp3")
samples = Sidereon.GNSS.SP3.precise_ephemeris_samples(sp3)
{:ok, source} = Sidereon.GNSS.PreciseEphemeris.from_samples(samples)
Build canonical precise-interpolant artifact bytes from this SP3 product.
Options:
:gap_threshold_factor- override the position-interpolation gap threshold factor recorded in the artifact header.
Return observed/predicted status derived from the SP3 record flags.
:epochs contains one entry per parsed epoch with :observed,
:orbit_predicted_satellites, and :clock_predicted_satellites. The
:observed_through split-Julian-date map is the last epoch before the first
predicted record, or the final epoch for a fully observed product. It is
nil when the first epoch is already predicted or the product is empty.
This metadata uses the file's actual per-record P flags and never assumes a
fixed ultra-rapid observed duration. Per-cell flags are also available from
state/3 and states_at/2.
@spec record_accuracy(t(), String.t(), non_neg_integer()) :: {:ok, Sidereon.GNSS.SP3.RecordAccuracy.t()} | {:error, term()}
Return decoded P/V standard deviations for one retained satellite record.
@spec record_accuracy_codes(t(), String.t(), non_neg_integer()) :: {:ok, Sidereon.GNSS.SP3.RawRecordAccuracy.t()} | {:error, term()}
Return raw signed P/V accuracy codes and their source bases for one record.
Return the SP3/RINEX satellite identifiers declared by the product header.
These are canonical three-character tokens such as "G01", "E12", or
"C30". The list is read from the already-loaded SP3 handle; no file I/O or
interpolation is performed.
Examples
{:ok, sp3} = Sidereon.GNSS.SP3.parse(sp3_bytes)
ids = Sidereon.GNSS.SP3.satellite_ids(sp3)
"G01" in ids
Alias for satellite_ids/1, matching the Python/WASM satellites accessor.
Epochs of the position nodes that some interpolation of satellite in the
inclusive J2000-seconds window selects, ascending; [] when no query in the
window is served for it.
The core applies the position interpolator's own serving and node-selection rule to the satellite's node series, under the product's interpolation options. Merge continuity verdicts read the merged product's nodes this way.
@spec selected_state_at_epoch_queries( t(), String.t(), Sidereon.GNSS.Time.ExactEpochQuery.t(), Sidereon.GNSS.Time.ExactEpochQuery.t() ) :: {:ok, nil | map()} | {:error, term()}
Read selected state and group delay at independent exact state and selection queries.
@spec state(t(), String.t(), non_neg_integer()) :: {:ok, Sidereon.GNSS.SP3.State.t()} | {:error, term()}
Return the exact parsed state of sat_id at epoch_index.
epoch_index is zero-based into epochs_j2000_seconds/1. This accessor does
no interpolation: the returned state is the record stored in the SP3 file,
including optional velocity, optional clock-rate, and the SP3 status flags.
Missing all-zero orbit records are not fabricated; querying such a cell returns
{:error, {:unknown_satellite, sat_id}}.
Returns {:ok, %Sidereon.GNSS.SP3.State{}} or {:error, reason}.
@spec states_at(t(), non_neg_integer()) :: {:ok, [{String.t(), Sidereon.GNSS.SP3.State.t()}]} | {:error, term()}
Return all exact parsed states at epoch_index.
The result is an ascending satellite-id list of {satellite_id, state} pairs
for records actually present at that SP3 epoch. Satellites whose position
record is the SP3 missing-orbit sentinel are absent from the list.
Returns {:ok, [{satellite_id, %Sidereon.GNSS.SP3.State{}}]} or
{:error, reason}.
Return the time reach of the product's position-interpolation stencil.
The core derives both extents from the parsed epoch interval and the same node count used by the interpolator. Callers never supply a stencil width.
@spec to_iodata( t(), keyword() ) :: {:ok, iodata()} | {:error, writer_error()}
Serialize the product to standard SP3-c / SP3-d text as iodata. Pure, no I/O.
In 3.0 this returns {:ok, iodata} or {:error, {tag, fields}}. Match the
result and unwrap the iodata before passing it to an iodata consumer; see the
README migration example.
This is the inverse of load/1 / parse/1: a read → (merge/2) → write
pipeline round-trips to a single standard SP3 file any reader consumes. The
output is deterministic (same product → identical bytes). A satellite absent
at an epoch is written as the SP3 missing-orbit sentinel, so a quarantined
merge/2 cell re-reads as missing, never a fabricated position. A satellite
holding a clock and no position at an epoch is written as that clock-only
record and reads back as one.
The writer states back the header descriptors the product holds and checks
every numeric field by reading its column back the way the reader does. A
value its column cannot state bit for bit, a value that would read back as one
of the format's absence sentinels, an epoch no record restates exactly, or
text that would not survive the reader's trim is refused by name rather than
rounded, shifted or dropped. A mean-combined merge/2 product commonly holds
positions finer than the millimetre columns and is refused for that reason;
a precedence merge keeps each contributor's own values.
Returns {:ok, iodata} or {:error, {tag, fields}}, tag naming the refusal
and fields holding every field it carries. The record-field refusals are
:record_value_not_representable, :record_value_too_wide,
:record_value_non_finite, :record_reads_as_absent and
:record_fields_disagree, each with field, satellite and epoch_index;
the epoch refusals are :epoch_not_restatable (with field_seconds and
residual_s),
:epoch_time_scale_mismatch and :year_not_representable;
:accuracy_not_representable carries satellite, epoch_index,
component, exponent and the core message; :accuracy_record_mismatch
and :accuracy_basis_missing carry satellite, epoch_index and message.
:satellite_not_representable carries the rendered identifier and its separate
constellation system and numeric prn, since the identifier cannot be
written as a two-digit SP3 token. The remaining entries name the header field
or count they concern.
A double the BEAM cannot hold, such as
the NaN residual of an epoch no candidate record reads back, is
{:nonfinite, bits}. A refusal this binding predates is
{:unhandled, %{message: text}} with the core's own text.
Examples
{:ok, sp3} = Sidereon.GNSS.SP3.load("igs.sp3")
{:ok, iodata} = Sidereon.GNSS.SP3.to_iodata(sp3)
{:ok, reparsed} = Sidereon.GNSS.SP3.parse(IO.iodata_to_binary(iodata))
Sidereon.GNSS.SP3.satellite_ids(reparsed) == Sidereon.GNSS.SP3.satellite_ids(sp3)
#=> true
@spec to_sp3_string( t(), keyword() ) :: {:ok, binary()} | {:error, writer_error()}
Serialize the product to an SP3 text binary.
In 3.0 this returns {:ok, text} or {:error, {tag, fields}}; callers
should match and unwrap the result. See the README migration example.
Returns {:ok, text} or the refusal to_iodata/2 documents.
Read the clock used for transmission placement at exact state and selection queries.
@spec validate_exact(t(), Sidereon.GNSS.SP3.ExactRequest.t()) :: {:ok, exact_coverage()} | {:error, term()}
Validate an already parsed SP3 product against an exact request.
The returned coverage atom has the same meaning as parse_exact/2.