# `GeoGenius.Providers.SimpleMaps.Rows`
[🔗](https://github.com/agoodway/geo_genius/blob/main/lib/geo_genius/providers/simplemaps/rows.ex#L1)

Turns one staged SimpleMaps row into the areas it describes.

This is the only module that knows SimpleMaps column names, so a source
that renames a column is a change here and nowhere else.

Both files denormalise the hierarchy into every row, so one row is several
areas: a `uscities` row is a city, every county it falls in, and its state;
a `uszips` row is a ZIP, every county it touches, and its state. A ZIP row
also names a city, but only as the mailing name USPS prefers for that ZIP,
which is why it becomes a name on the ZIP rather than a city area -- the
`uscities` file is where a city gets its identity, and inventing a second
one from a ZIP row would key the same city twice.

A state is keyed under the authority that defines its code. Most states key
under `census` and carry an `ansi_state` code. Six do not. `AA`, `AE` and
`AP` are USPS constructs for military mail -- Armed Forces Americas, Europe
and Pacific. `FM`, `PW` and `MH` are the Freely Associated States --
Micronesia, Palau and the Marshall Islands -- sovereign countries the USPS
serves rather than states of any kind. The Census defines none of the six
and assigns none of them an ANSI code, so keying them under `census` with
an `ansi_state` code would assert two things no source says. They key under
`usps` and carry a `usps_state` code instead. The code is carried rather
than omitted because it is true and it leaves a host the same external-code
join every other area offers; an area with no code at all would be
reachable only by key. Their area type defaults to `state` for compatibility,
but `areas/2` and `edges/2` accept the separately declared type selected by
the SimpleMaps manifest's `non_census_state_area_type` option.

Nothing else about them changes: a name is read from `state_name` the way
every other state's is. `FM`, `PW` and `MH` carry one and get it. `AA`, `AE`
and `AP` publish nameless: `state_name` is blank in every one of their
`uszips` rows, and `uscities` carries no row at all for any of the six
codes, so nothing anywhere supplies a name and none is invented to fill
the gap. Supplying
"Armed Forces Europe" out of a table in this library would manufacture a
fact the source does not carry, the same way a centroid copied off a row
would. Those three exist so their ZIPs have a parent, and a host that wants
a label for them supplies its own.

A county belongs to the state its FIPS is assigned within -- the first two
digits of a five-digit county FIPS -- and not to whatever state the row
carrying it names. The two disagree wherever a mailing address crosses a
state line, and `GeoGenius.Providers.SimpleMaps.Fips` is where the prefix is
read. A row whose county lies in another state yields that state as an area
of its own beside the row's, so the hierarchy `edges/1` asserts from the
same prefix can only name areas `areas/1` produced.

A county and a state are read out of columns rather than off rows of their
own, so they carry only what those columns say: their code, their name, and
their FIPS or ANSI code. They get no centroid, because the row's `lat`/`lng`
measures the city or ZIP and nothing else; a centroid copied from the row
would make `areas_near` return a fact nobody measured. They get no
attributes for the same reason -- the demographic columns of a row describe
the city or ZIP, and hanging them on the county would let the last row
imported decide what the county's median income is.

`edges/2` reads the same county columns `areas/2` does, through the same
helpers, so the hierarchy this provider asserts can only ever name keys
`areas/2` produced.

This module knows SimpleMaps column names and how to parse them.
`GeoGenius.Providers.SimpleMaps.Validation` reuses `list/2`, `field/2`,
`label/1`, and the two counties-column-name accessors here rather than
re-deriving them, so the two modules can never disagree about how a column
is read. `areas/2` runs `Validation.check/1` before building anything, so a
row whose county columns contradict each other fails the release instead of
being silently reconciled.

# `areas`

```elixir
@spec areas(GeoGenius.Staging.Row.t()) ::
  {:ok, [GeoGenius.Provider.Area.t()]} | {:error, String.t()}
```

Returns the areas one staged row describes, in hierarchy order.

A `uscities` row yields its city, its counties, and its state; a `uszips`
row yields its counties, its ZIP, and its state. Any other artifact is an
error, since this provider parses only the two files it declares.

`Validation.check/1` runs first, so a row whose county columns contradict
each other -- a primary `county_fips` missing from `county_fips_all`, a
counties column of a different length than `county_fips_all`, or a ZIP's
`county_weights` naming a county set that disagrees with it -- is an error
before anything is built from it.

A row whose counties do not all lie in the state it names yields the states
they do lie in as well, one area each, read from their FIPS prefixes. A ZIP
in the District of Columbia naming a county in Virginia describes Virginia,
however little of it, and the county has to hang somewhere.

A row whose city, ZIP, or state code is blank is an error rather than an
area: `area_key` is `<authority>:<area_type>:<code>`, so a blank code would
put every such row under one shared, meaningless key.

That is a deliberate divergence from `GeoGenius.Providers.CSV`, which
returns `:skip` for a row whose code column is blank. A CSV collection is
described by a manifest that may name a column the source populates only
sometimes, so skipping is the right read of a gap there. SimpleMaps
populates these columns in every row of both files, so a blank one is a
corrupt or truncated download rather than a shape to tolerate, and failing
the release is better than publishing a hierarchy missing the rows nobody
was told about. The error names the column and enough of the row to find
it in a file of tens of thousands.

# `areas`

```elixir
@spec areas(GeoGenius.Staging.Row.t(), String.t()) ::
  {:ok, [GeoGenius.Provider.Area.t()]} | {:error, String.t()}
```

Like `areas/1`, with the area type selected for the six USPS-only state codes.

# `edges`

```elixir
@spec edges(GeoGenius.Staging.Row.t()) :: [{String.t(), String.t(), String.t()}]
```

Returns the relation edges one staged row asserts, parent first.

SimpleMaps carries centroids and no boundaries, so nothing here can be
measured by overlap; the FIPS columns are the only statement of hierarchy
the source makes, and these edges are that statement.

A `uscities` row asserts one state-to-county edge per county it names and
one county-to-city edge per county; a `uszips` row asserts the same
state-to-county edges and a county-to-zip edge per county. The state on a
county's edge is the one its own FIPS prefix names, never the one the row
carrying it does, and a county whose prefix names no state this provider
knows gets no state edge at all rather than a false one. A ZIP row's
`city` column is the mailing name USPS prefers for that ZIP, so no edge is
asserted between a city and a ZIP in either direction -- a ZIP crosses city
lines, and neither one contains the other.

Every edge names the parent first, the order
`GeoGenius.Catalog.put_relation/4` takes: a swapped pair writes
successfully and silently inverts `GeoGenius.children_of/2` and
`GeoGenius.ancestors_of/2`.

A row naming no county asserts one edge instead: its state contains the ZIP
directly. A military or Freely Associated States ZIP is in its state and in
no county, so the state is the only parent it has, and hanging it there is
truer than leaving it with no parent at all. That edge is emitted only for
a row with no county -- where counties exist they already connect the ZIP,
and a state-to-zip edge beside them would assert a second path to the same
place. A `uscities` row never takes this path, because
`GeoGenius.Providers.SimpleMaps.Validation.check/1` fails a city row that
names no county before this is reached.

Returns `[]` for an artifact this provider does not parse, since
`areas/1` is what reports that as an error.

# `edges`

```elixir
@spec edges(GeoGenius.Staging.Row.t(), String.t()) :: [
  {String.t(), String.t(), String.t()}
]
```

Like `edges/1`, with the area type selected for the six USPS-only state codes.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
