A versioned catalog of named geographic areas for Elixir and PostgreSQL/PostGIS applications.
Summary
Functions
Areas above this one. Accepts :classifications and :max_depth.
Areas above each of these, one call instead of one call per key. Seed
attribution and options match children_of_many/2.
Areas carrying a code.
Areas carrying any of these code values under one code type, one call instead of one call per value.
Areas overlapping a geometry, with the measured overlap of each.
Areas whose boundary covers a point.
Areas within a radius of a point, nearest first. Accepts :limit, nil for none.
The local file one artifact of a release resolves to.
Polls the catalog for a run's outcome, in the calling process.
Areas below this one. Accepts :classifications and :max_depth.
Areas below each of these, one call instead of one call per key.
Publishes a verified release, returning its id.
The currently published release for a collection, or nil if none is published.
Areas related to this one in either direction, one level out.
Areas related to each of these in either direction, one call instead of one
call per key. Seed attribution and options match children_of_many/2.
Every artifact the collection's published release was built from, with where each one resolves to on this machine, ordered by logical name.
The release a collection had published at a moment, or nil if it had
published nothing yet. Requires :collection.
Resolves a mixed input through the cascade, returning the first strategy that matched.
Explicitly replaces a failed latest attempt with the supplied exact manifest and enqueues the newly claimed attempt.
Rolls a collection's publication back to its previous release, returning that release's id.
Areas ranked by name similarity. Accepts :limit, nil for none.
One import run, or nil when no run carries that id.
Verifies that the database satisfies GeoGenius's prerequisites.
Verifies prerequisites and raises GeoGenius.PreflightError when any are unmet.
Functions
@spec ancestors_of(String.t(), keyword()) :: [GeoGenius.AreaMatch.t()]
Areas above this one. Accepts :classifications and :max_depth.
@spec ancestors_of_many(GeoGenius.Store.seeds(), keyword()) :: [ GeoGenius.SeededMatch.t() ]
Areas above each of these, one call instead of one call per key. Seed
attribution and options match children_of_many/2.
@spec areas_by_code(String.t(), String.t(), keyword()) :: [GeoGenius.AreaMatch.t()]
Areas carrying a code.
A code is unique only within a parent, so :parent_area_key scopes the
lookup and :parent_max_depth controls how far down it reaches. Always
returns a list: several areas can legitimately share one code.
@spec areas_by_code_many(String.t(), GeoGenius.Store.seeds(), keyword()) :: [ GeoGenius.SeededMatch.t() ]
Areas carrying any of these code values under one code type, one call instead of one call per value.
The seed here is the code value, not an area key, so :seed_key holds the
value that found each row. Options match areas_by_code/3, including
:parent_area_key and :parent_max_depth, which apply to every value in
the list.
@spec areas_for_geometry(Geo.geometry(), keyword()) :: [GeoGenius.AreaMatch.t()]
Areas overlapping a geometry, with the measured overlap of each.
@spec areas_for_point(GeoGenius.Store.numeric(), GeoGenius.Store.numeric(), keyword()) :: [ GeoGenius.AreaMatch.t() ]
Areas whose boundary covers a point.
Longitude first. Coordinates may be floats, integers, or the %Decimal{}
a numeric column loads as. Options: :collections, :types,
:release_id, :include_retired, plus :repo and :prefix.
@spec areas_near( GeoGenius.Store.numeric(), GeoGenius.Store.numeric(), GeoGenius.Store.numeric(), keyword() ) :: [GeoGenius.AreaMatch.t()]
Areas within a radius of a point, nearest first. Accepts :limit, nil for none.
@spec artifact_path(String.t(), String.t(), keyword()) :: {:ok, Path.t()} | {:error, GeoGenius.ArtifactError.t()}
The local file one artifact of a release resolves to.
Returns {:error, %GeoGenius.ArtifactError{reason: :not_cached}} naming both
the expected path and the cache key when nothing is there yet, rather than a
path to a file that does not exist.
@spec await(Ecto.UUID.t(), timeout() | nil, keyword()) :: {:ok, GeoGenius.ImportRun.t()} | {:error, GeoGenius.ImportRun.t()} | {:error, :timeout}
Polls the catalog for a run's outcome, in the calling process.
Returns {:ok, run} once GeoGenius.ImportRun.finished?/1 is true and the
run succeeded, {:error, run} once it finished and failed, and
{:error, :timeout} once timeout elapses with neither. Polls the catalog
every 250ms rather than monitoring a process: the run may be executing on
another node entirely, and the durable row in PostgreSQL is the only thing
both sides can see.
timeout resolves in this order: the argument given here, then
config :geo_genius, :await_timeout, then the library default of
1,800,000ms (thirty minutes) -- comfortably past the ~17 minutes a full US
SimpleMaps import takes, without blocking a caller that never says
otherwise for the length of a genuinely hung run. :infinity is accepted
at every level for a caller willing to wait as long as it takes.
@spec children_of(String.t(), keyword()) :: [GeoGenius.AreaMatch.t()]
Areas below this one. Accepts :classifications and :max_depth.
@spec children_of_many(GeoGenius.Store.seeds(), keyword()) :: [ GeoGenius.SeededMatch.t() ]
Areas below each of these, one call instead of one call per key.
Every row carries the seed it came from in GeoGenius.SeededMatch's
:seed_key, because a result mixes rows from every seed. A seed with no
children contributes no row rather than an empty one, and the rows come back
in the order the seeds were given. Takes the same options as
children_of/2.
Resolving a list this way collapses N round trips to one, which is the whole
of what it saves: the SQL still runs the singular read once per seed, and
the planner cannot push a predicate into a SETOF function either way. A
caller that wants to join catalog areas against its own tables, or aggregate
over them, wants GeoGenius.Query's composable path instead of this.
GeoGenius.children_of_many(county_keys)
|> Enum.group_by(& &1.seed_key, & &1.match)
@spec import(keyword()) :: {:ok, Ecto.UUID.t()} | {:error, term()}
Atomically registers an exact release candidate, claims its import run, and
enqueues it. Never runs the import itself -- runner.enqueue/3 starts that,
and status/2 or await/3 reads it back.
opts[:manifest] is used as given; otherwise the manifest is loaded via
GeoGenius.Manifest.load/3 from opts[:collection] and opts[:release]
(both required in that case). A completed unpublished candidate returns its
existing run without enqueueing, while the owner of a live run may safely
submit that same run to its runner again.
Repeating an import whose latest attempt failed returns
{:error, %GeoGenius.CandidateError{reason: :failed}} with that attempt's
run id. It never creates a replacement implicitly; pass the failed run id
to retry_failed/2 to create the next attempt.
:owner defaults to to_string(node()). Repeating the exact request under
the same owner closes a post-commit enqueue gap by delivering the existing
run again; the run's executor claim makes a duplicate delivery a no-op, not
a takeover. A caller running two importers on one node passes :owner
explicitly. :stale_after_seconds (default 900) is the window used to
diagnose an abandoned run; staleness never transfers its executor or
creates a retry. The value also travels through the runner's args so
GeoGenius.Pipeline can derive its statement timeout from the original
claim. :publish (default false) is forwarded the same way.
Returns {:error, exception} -- never raises -- for a manifest that
cannot be resolved (GeoGenius.ManifestError) or a catalog write that
cannot be completed (GeoGenius.CatalogError). Expected candidate
refusals, including a published release, return
GeoGenius.CandidateError with a closed :reason and the conflicting
release/run identifiers. A runner enqueue failure returns
GeoGenius.EnqueueError, including the run id, backend, acceptance
certainty, original reason, and safe status/redelivery guidance.
@spec publish(Ecto.UUID.t(), keyword()) :: {:ok, Ecto.UUID.t()} | {:error, term()}
Publishes a verified release, returning its id.
The SQL publish_release re-runs the release's verification inside the
publication, so this is one of the library's long single statements.
:timeout bounds it, in milliseconds, and defaults to 900,000 -- the window
GeoGenius.Pipeline gives the same statement in its verifying phase --
rather than DBConnection's fifteen-second default, which a national release
exceeds by orders of magnitude.
Returns {:error, exception} -- never raises -- when the release fails
verification or the catalog write cannot be completed.
Only the publication is guarded. The collection key travelling in the
:release_published payload is read back after the publication has already
committed, and a read that fails there costs the payload its
:collection_key, never the outcome: a publication that committed is
reported as {:ok, release_id} and the event still fires.
@spec published_release(String.t(), keyword()) :: Ecto.UUID.t() | nil
The currently published release for a collection, or nil if none is published.
@spec release_artifacts(String.t(), keyword()) :: {:ok, [GeoGenius.ReleaseArtifacts.Artifact.t()]} | {:error, GeoGenius.ArtifactError.t()}
Every artifact the collection's published release was built from, with where each one resolves to on this machine, ordered by logical name.
:release_id reads a release other than the published one, which is how a
host fills a projection for a release before it goes live. See
GeoGenius.ReleaseArtifacts and guides/projections.md.
@spec release_at(DateTime.t(), keyword()) :: Ecto.UUID.t() | nil
The release a collection had published at a moment, or nil if it had
published nothing yet. Requires :collection.
Reads take a release id rather than a timestamp, so this is how a caller
holding a timestamp gets an argument for :release_id.
@spec resolve(map(), keyword()) :: [GeoGenius.AreaMatch.t()]
Resolves a mixed input through the cascade, returning the first strategy that matched.
input carries whatever the caller holds: lon and lat together,
code_type and code_value together, name, and radius_m for the
proximity strategy. parent_area_key is not a signal of its own. It scopes
the code and name strategies to one area's descendants; containment and
proximity ignore it, holding a coordinate already.
Accepts :strategies to constrain or reorder the cascade.
@spec retry_failed(Ecto.UUID.t(), keyword()) :: {:ok, Ecto.UUID.t()} | {:error, term()}
Explicitly replaces a failed latest attempt with the supplied exact manifest and enqueues the newly claimed attempt.
The manifest and runner options resolve exactly as they do for import/1.
Candidate mismatches, protected releases, non-latest attempts, and runs that
are not failed return a structured GeoGenius.CandidateError without
enqueueing anything.
@spec rollback(String.t(), keyword()) :: {:ok, Ecto.UUID.t()} | {:error, term()}
Rolls a collection's publication back to its previous release, returning that release's id.
Returns {:error, exception} -- never raises, and never clears the
publication -- for a collection with no previous release to roll back to,
and {:error, String.t()} for a collection key the catalog does not carry.
Only the rollback is guarded. The release the collection now publishes is
read back afterwards, and a read that fails there returns
{:error, {:unread, message}}, a shape distinct from every failure shape
above: the publication moved whether or not the id can be read, so a caller
matching this clause knows the rollback happened and must not retry it. The
message says the same thing in words. GeoGenius.Pipeline uses
{:error, {:unrecorded, message}} for its own committed-but-unread outcome;
this is that pattern at the public API. The :release_rolled_back event
fires either way, carrying a nil :release_id when the read failed.
@spec search_areas(String.t(), keyword()) :: [GeoGenius.AreaMatch.t()]
Areas ranked by name similarity. Accepts :limit, nil for none.
@spec status(Ecto.UUID.t(), keyword()) :: GeoGenius.ImportRun.t() | nil
One import run, or nil when no run carries that id.
Verifies that the database satisfies GeoGenius's prerequisites.
Returns :ok, or {:error, reasons} naming every unmet prerequisite.
Verifies prerequisites and raises GeoGenius.PreflightError when any are unmet.