feregion maps longitude/latitude coordinates to Flinn-Engdahl (FE)
geographical regions and their parent seismic regions. It provides scalar,
NumPy batch, hierarchy-conversion, pandas, CSV, and optional GeoJSON interfaces.
Normal lookup uses packaged processed data. Runtime lookup, names, hierarchy conversion, and GeoJSON generation do not require ObsPy, ISC network access, or another source service. pandas and Shapely remain optional interface dependencies.
Add the core package:
uv add feregionAdd an optional interface when needed:
uv add 'feregion[pandas]'
uv add 'feregion[geo]'For a Git checkout with its committed lock:
uv sync --lockedA clean source handoff intentionally omits uv.lock. An unlocked uv sync may
create a local dependency solution for inspection, but that state is not normal
repository verification evidence. The repository uses uv directly and does
not use a Makefile.
import numpy as np
import feregion
feregion.lookup_number(12.0, 48.0)
# 543
feregion.lookup_region(12.0, 48.0)
# GeographicRegion(number=543, name='GERMANY')
coordinates = np.array(
[
[12.0, 48.0],
[-60.0, -30.0],
]
)
numbers = feregion.lookup_numbers(coordinates)
# array([543, 133], dtype=uint16)
feregion.numbers_to_names(numbers)
# array(['GERMANY', 'NORTHEASTERN ARGENTINA'], dtype='<U...')
feregion.lookup_seismic_number(12.0, 48.0)
# 36
feregion.lookup_seismic_region(12.0, 48.0)
# SeismicRegion(number=36, name='Northwestern Europe')
feregion.geographic_to_seismic_number(543)
# 36A coordinate array always uses [longitude, latitude] column order. By package
convention, these values are interpreted as WGS84 geographic degrees. The
package does not transform coordinates between CRSs, and the WGS84 convention
is separate from the historical FE degree-grid definition. Use batch lookup
when throughput matters.
Scalar lookup accepts Python integer/float values and NumPy integer/floating scalars. Boolean values are rejected at runtime. Direct geographical number-to-name and geographical-to-seismic operations accept only identifiers that are active in the lookup table; retired identifiers 172, 299, and 550 are not valid direct geographical inputs even though historical name slots are retained in the packaged source-derived name array.
The pre-existing generic functions (lookup_number, lookup_region,
lookup_numbers, number_to_name, and numbers_to_names) remain geographical
compatibility interfaces. New code can use the explicit geographic_* and
seismic_* forms when the level matters.
Plural region-number conversion functions accept NumPy-compatible array-like
values and always return NumPy arrays. A scalar input returns a
zero-dimensional array. Use geographic_number_to_name(),
geographic_to_seismic_number(), or seismic_number_to_name() when a
Python scalar result is required.
Packaged geographical names are derived from pinned ObsPy names.asc; packaged
seismic names retain the declared ISC FE standards spelling. The project does
not claim that either source provides the unique historical FE naming convention.
Explicit FlinnEngdahlLookup(table, names) construction remains valid and is
geographical-only. Supplying the optional seismic arrays adds hierarchy
capability; a custom geographical-only engine does not silently borrow packaged
seismic data.
from feregion.pandas import lookup_dataframe
result = lookup_dataframe(frame)
result_with_names = lookup_dataframe(frame, include_names=True)
seismic = lookup_dataframe(frame, level="seismic", include_names=True)The adapter requires distinct, unique longitude and latitude columns. Boolean coordinate columns are rejected. Supported numeric dtypes retain their source precision through validation and FE cell-ownership selection, including extended floating dtypes. This prevents a wide floating value immediately beside an integer-degree boundary from being rounded into the adjacent FE cell. Output columns are additive and cannot replace existing input columns.
uv run --locked fe-region point 12 48 --name
uv run --locked fe-region point 12 48 --level seismic --name
uv run --locked fe-region csv input.csv -o output.csv --level seismic --include-names
uv run --locked --extra geo fe-region geojson .cache/regions.geojson \
--level seismic --properties seismic_number seismic_nameFilesystem CSV input must be valid UTF-8 and satisfy strict CSV syntax. CSV
headers must contain unique field names. Every data row must contain exactly
the number of fields declared by the header. Filesystem output uses atomic
publication through a temporary sibling and os.replace(). Existing destination
permission bits are preserved. New files use normal process-umask semantics.
A failed filesystem conversion does not publish a new partial destination. stdout is a streaming sink and can contain earlier rows if a later row fails. The filesystem guarantee does not claim crash durability, directory fsync, ACL preservation, or owner preservation.
With the packaged lookup, GeoJSON emits 754 geographical features or 50
seismic features from the same one-degree cell grid. Geometry level and feature
annotations are independent. Default features contain the selected level's
number and name.
CLI examples:
# Default geographical regions with number and name.
uv run --locked --extra geo fe-region geojson .cache/geographic.geojson
# Compact seismic output with several properties after one option.
uv run --locked --extra geo fe-region geojson .cache/seismic.geojson \
--level seismic --properties seismic_number seismic_name --no-metadata
# Include active geographical children as number/name objects and pretty-print.
uv run --locked --extra geo fe-region geojson .cache/seismic-with-children.geojson \
--level seismic --properties seismic_number seismic_name geographic_regions \
--label number-name --indent 2
# Ask for every property valid at the selected level.
uv run --locked --extra geo fe-region geojson .cache/geographic-full.geojson \
--properties all --indent 2The older repeatable --property NAME form remains supported for scripts, but
--properties NAME [NAME ...] is the preferred form when selecting several
fields. At seismic level, geographical child membership is represented only by
geographic_regions, whose value is a list such as
[{"number": 543, "name": "GERMANY"}, ...]. Scalar geographical properties and
the old parallel geographic_numbers / geographic_names lists are rejected.
Python API examples:
from feregion.geojson import regions_geojson, write_regions_geojson
# Geographical features with explicit parent seismic fields.
geographic = regions_geojson(
properties=(
"geographic_number",
"geographic_name",
"seismic_number",
"seismic_name",
),
label="number-name",
)
# Seismic features with structured geographical child regions.
seismic = regions_geojson(
level="seismic",
properties=("seismic_number", "seismic_name", "geographic_regions"),
)
# Write a compact machine-oriented seismic file.
write_regions_geojson(
"seismic.geojson",
level="seismic",
properties=("number",),
include_metadata=False,
)The Python GeoJSON API also accepts explicit lookup engines; their feature populations and cross-level membership follow that engine. Collection metadata identifies FE-1995 only when the selected engine is the packaged default instance. Other explicit engines use null scheme/revision fields rather than inferred provenance. The geometry is area-equivalent. Numeric lookup is authoritative for coordinates exactly on integer cell boundaries because ordinary closed polygons cannot encode every directional FE point-boundary rule.
Run the canonical lock-preserving local checks with uv:
uv sync --locked --group dev
uv run --locked ruff format --check .
uv run --locked ruff check .
uv run --locked mypy
uv run --locked pytest -q
uv run --locked pytest -q --cov=feregion --cov-branch --cov-report=term-missing
uv builduv build has no lock flag; lock freshness is established by the preceding
uv sync --locked. Use unlocked dependency operations only when intentionally
creating or refreshing uv.lock, not as verification evidence.
Run the local compatibility matrix before a compatibility-sensitive push:
uv sync --locked --group dev
uv run --locked --group matrix tox runThe tox-uv matrix uses the standard py311, py312, py313, and py314
environment names plus a Python 3.11 minimum environment. Normal compatibility
environments are lock-backed. The minimum environment intentionally bypasses
the lock and resolves the project plus its test extra together with
lowest-direct, so NumPy and pandas lower bounds are selected in one dependency
transaction. The environment prints its resolved Python, NumPy, pandas, Shapely,
and pytest versions before running tests. The special lower-bound environment is
recreated for each run so stale tox installer state cannot affect resolution. Run
only that environment with uv run --locked --group matrix tox run -e minimum.
uv-backed tox environments can use uv-managed Python interpreters, so the required
interpreters do not all need to be installed manually in advance.
GitHub Actions verifies Python 3.11, 3.12, 3.13, and 3.14. Dedicated jobs also run the installed ObsPy oracle and the same tox-uv minimum-dependency environment. A separate quality job runs Ruff, mypy public typing, distribution builds, wheel archive inspection, and dependency-isolated wheel verification. A scheduled/manual job performs the live ISC semantic comparison without making pull-request tests network-dependent.
uv.lock is committed and is the dependency-resolution authority for normal
repository and CI environments. Use --locked in verification workflows so a
stale lock fails instead of being refreshed implicitly.
Generated runtime assets are version-controlled. Downloaded upstream source material is not. Maintainers retrieve both source families before complete asset regeneration:
uv run --locked python -m tools.fetch_obspy_fe_data
uv run --locked python -m tools.fetch_isc_fe_regions
uv run --locked python -m tools.build_assetsThe ObsPy fetcher pins tag 1.4.2 at immutable commit
a629e8c021052904b6b8d62699d03f2a3721ae63 and verifies source-file SHA-256
values. The ISC fetcher parses the declared FE standards page, validates all 50 seismic
regions and 754 active geographical memberships, and compares the normalized
content with a literal reviewed semantic SHA-256. The digest is not recomputed
automatically from the Python hierarchy declarations it protects, so a coherent
name/membership edit cannot silently redefine the expected source.
tools/build_assets.py converts verified retrieved sources into the four runtime
arrays shipped with the package.
The source-data license status remains unresolved where an explicit reuse grant
has not been established. See src/feregion/data/metadata.json and
THIRD_PARTY_NOTICES.md.
Use the repository development environment and install the Git hooks once:
uv sync --locked --group dev
uv run --locked pre-commit installThe pre-commit pipeline formats Python files with Ruff, runs Ruff lint checks,
runs mypy over the public package modules and downstream-consumer typing fixture,
and then delegates behavioral tests to tox's local environment. Hosted quality
verification runs the same static evidence because the wheel ships py.typed. Run the
commit-time pipeline manually with:
uv run --locked pre-commit run --all-filesThe ASV-derived path is the primary benchmark authority; the 0.4 migration is closed.
The 0.4 benchmark system owns stable semantic cases, historical adapters, a maintained
1-2-5 load grid, environment/comparability checks, retained raw samples and provenance,
and a project release-regression decision. ASV provides revision/environment mechanics,
timing, history, and the derived static report.
Operational benchmark commands are intentionally single-sourced in
docs/benchmark-operations.md. In particular, the routine release gate requires the
accepted prior candidate explicitly at execution time; advancing a candidate no longer
requires editing benchmark campaign source. The ordinary refresh is bounded, while
integration, promotion, history, and diagnostics are selected only when the decision
needs that evidence.
benchmarks/README.md describes benchmark architecture and contributor-facing semantics.
docs/benchmark-results.md summarizes bounded observed evidence,
docs/obspy-or-feregion.md gives user-selection guidance, and
docs/benchmark-migration-parity.md preserves the completed migration record.
The predecessor standalone/pytest-benchmark/Tox tools remain available for compatibility, investigation, and historical provenance; they are not co-equal release authority.
To hand the current repository source to another reviewer or agent without local
caches, environments, run output, IDE files, or uv.lock, run:
uv run --locked python -m tools.export_repositoryThe exporter packages only Git-tracked paths, but reads their current
working-tree bytes, so tracked Ruff/pre-commit edits do not need a special
filesystem cleanup first. By default it writes
dist/feregion-v<version>-<YYYY-MM-DD>-handoff.zip, using the package version
from pyproject.toml and the current UTC date. It warns about non-ignored
untracked files; stage or commit genuine new source before handoff, or use
--fail-on-untracked to require a clean tracked-source boundary. Use --output
when a workflow requires another destination.
The maintained contract set uses stable filenames. Git history records document revisions:
docs/feregion-requirements.md;docs/feregion-engineering-requirements.md;docs/feregion-repository-delivery-requirements.md;docs/feregion-design.md;docs/feregion-quality-assurance.md;docs/feregion-decisions.md; anddocs/feregion-verification-traceability.md.
docs/testing.md is the general maintainer procedure and docs/benchmark-operations.md is the benchmark operator runbook. When behavior changes, update the affected maintained documents in the same repository change.
The feregion project is distributed under LGPL-3.0-only. That project license
does not by itself establish the license of upstream FE source data. See
THIRD_PARTY_NOTICES.md and src/feregion/data/metadata.json for the recorded
provenance and limitation.