Exporting is importing run backwards. Where an import
reads the outside world and translates it into your internal vocabulary, an
export reads a fully-identified internal time series
and translates it back out — renaming through the same ID map,
converting units, and writing the result to a file, database, or service. This is
hop ⑤, the reverse of the import boundary: internal names
and units go in, the outside world’s names and units come out. This guide covers
the config file that drives it: timeSeriesExportRun.
The skeleton mirrors the import file element for element. A general block says
where the data goes and how to write it, followed by one or more timeSeriesSet
blocks that say which internal series to read out.
timeSeriesExportRun
└─ export
├─ general ← the destination + the writer + the mappings
The same mental split as import, pointed the other way: timeSeriesSet speaks
your language; general produces the outside world’s. The idMap is again the
bridge — only now it runs internal → external. Compare this tree with the
import anatomy; the shape is
identical, import/general/timeSeriesSet becoming export/general/timeSeriesSet.
Where import picks a reader, export picks a writer (also called a serializer).
The general block starts with three mutually exclusive ways to choose one — pick
exactly one:
Element
Use when
Example value
exportTypeStandard
The destination is one of FEWS’s built-in serializers.
pi, csv, netcdf
exportType
You’re using a general/named serializer that isn’t in the standard enum (the general tabular/CSV writer lives here).
We’ll write out the hourly discharge we imported in the
import walkthrough
— the same RiverGauge_01 / Q.obs series, now leaving FEWS. Every step is the
mirror of an import step.
Where import had folder + fileNamePatternFilter (which files to read),
export has folder + exportFileName (what file to write). $EXPORT_FOLDER$
is a global property (in global.properties) so the path isn’t hard-coded
per machine. Instead of a plain name you can build the filename from a
prefix/suffix, or set useExternalLocationIdAsName to write one file per
location.
The mirror of importTimeZone. Internally every value carries an explicit time,
but the file needs a zone to render into — declare the zone the receiver expects
with <timeZoneName> (e.g. GMT) or a <timeZoneOffset> like +01:00. Get it
wrong and every timestamp in the output is shifted.
idMapId names the ID map — but here it runs the other
direction, turning RiverGauge_01 / Q.obs back into whatever the receiver
calls them (say GAUGE-A / discharge). Add unitConversionsId to convert
from your stored unit to the receiver’s on the way out, and flagConversionsId
to translate FEWS quality flags into the receiver’s scheme.
Identical in form to the import’s timeSeriesSet, and that’s the point: it must
reconstruct the same header the
producer wrote, or it reads nothing. moduleInstanceId here is
ImportObservations — the module that created the series — not the export’s
own instance. This is the read side of the header-matching rule the
import guide warns about.
As with import, you rarely write one timeSeriesSet per gauge. Swap locationId
for a locationSetId and a single set exports the whole group, each member renamed
through the same ID map:
Combined with <exportFileName><useExternalLocationIdAsName>true</useExternalLocationIdAsName></exportFileName>,
that writes one file per location in the set.
Export to a service instead of a file. Replace the folder +
exportFileName pair with a serverUrl (with optional backupServerUrl and
connectionTimeOutMillis, plus user / password for protected servers). The
chosen serializer talks to that endpoint rather than writing to disk.
Different units for the receiver. Add unitConversionsId; FEWS converts from
your stored unit to the receiver’s on the way out — the exact mirror of a unit
conversion on import.
Exporting forecasts. Set timeSeriesType to external forecasting (or read
a simulated series), and use forecastSelectionPeriod to pick which forecasts to
write. Everything else is the same pipeline — see
the lifecycle.
Marking missing values. By default gaps export as NaN; set
exportMissingValue (numeric) or exportMissingValueString (text) to match what
the receiver expects, or omitMissingValues to leave gaps out entirely.
Controlling precision and separators.precision fixes the number of
fraction digits; columnSeparator / decimalSeparator tune tabular formats like
generalCsv (e.g. ; and , for European CSVs).
Nothing exports → the timeSeriesSet didn’t match a stored series. The read
side must reconstruct the exact header
the producer wrote — same moduleInstanceId, parameterId, locationId,
timeSeriesType, and timeStep. A single mismatched field reads an empty series
and writes an empty (or missing) file. This is the mirror of the import’s
“imports but doesn’t display” trap.
External names look wrong → the ID map, read backwards. The same map that
renamed data on import now renames it on export, internal → external. A map built
only with import in mind can produce surprising output names — see the
bidirectional note on ID mapping. Once any location is
mapped, the map also filters: unmapped series are silently dropped from the
export too.
Values off by a constant factor → missing unitConversionsId. Without an
outward unit conversion, values are written in their stored unit, which may not be
what the receiver expects.
readWriteMode is required, but inert here. The schema requires it on every
timeSeriesSet, so include it (read only is sensible for an export, which only
reads). Its semantics are honoured only by the Time Series Display; it does not
make the export module behave differently, so don’t rely on it to.
A change has no effect → a higher-versioned file wins. Check for a sibling
like ExportDischarge 1.01 default.xml. See
how FEWS finds a file.
Run the export (via a workflow, the same way you run an
import) and read the log. An empty read — no series matched the timeSeriesSet
— shows up here.
Open the output. Look in $EXPORT_FOLDER$/discharge/ for discharge.csv
(or the file the serializer produced) and confirm it exists and has rows.
Confirm the external names and units. The location and parameter columns
should show the receiver’s names (e.g. GAUGE-A / discharge), not your
internal RiverGauge_01 / Q.obs — that proves the ID map ran outward. Check
the magnitudes match the expected unit, and the timestamps the expected
exportTimeZone.