Metadata-Version: 2.3
Name: ruian-import
Version: 0.7.0
Summary: Download and parse Czech RUIAN (ČÚZK) data: regions, districts, ORP, POÚ, municipalities, city districts, boundary geometry, and senate electoral districts (ČSÚ)
Project-URL: Repository, https://gitlab.com/alexandra.tapkova/ruian-import
Author-email: Alexandra Ťapková <git@ouppy.space>
License: MIT
Keywords: cuzk,czech,gis,municipalities,ruian
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.10
Requires-Dist: requests>=2.28
Provides-Extra: dev
Requires-Dist: pytest; extra == 'dev'
Requires-Dist: responses; extra == 'dev'
Description-Content-Type: text/markdown

# ruian-import

Download and parse the Czech ČÚZK (RÚIAN) registries: regions, districts, ORP, POÚ, municipalities, and city districts (městské části / obvody), plus WGS84 boundary geometry for each level.

## Installation

```bash
pip install ruian-import
```

## Usage

```python
from ruian_import import fetch_regions, fetch_districts, fetch_municipalities

regions = fetch_regions()        # list[Region]
districts = fetch_districts()    # list[District]

for municipality in fetch_municipalities():   # Iterator[Municipality]
    ...
```

`fetch_regions()` and `fetch_districts()` return lists. `fetch_municipalities()`
and `fetch_municipalities_with_coordinates()` return one-shot iterators so the
~6,200 municipalities don't all sit in memory at once; wrap them in `list()` if
you need more than one pass.

### Data classes

```python
@dataclass
class Region:
    code: str              # e.g. "19"
    name: str              # e.g. "Hlavní město Praha"
    nuts_lau: str | None   # e.g. "CZ010"
    abolished: str | None  # abolishment date "YYYY-MM-DD", or None if active

@dataclass
class District:
    code: str
    name: str
    region_code: str
    nuts_lau: str | None
    abolished: str | None

@dataclass
class Municipality:
    code: str              # 6-digit municipality code, e.g. "554979"
    name: str
    district_code: str
    lat: float | None      # WGS84; None unless you fetch coordinates (see below)
    lon: float | None
    abolished: str | None

@dataclass
class CityDistrict:         # městský obvod / městská část (MOMC)
    code: str
    name: str
    municipality_code: str  # parent obec (statutární město)
    abolished: str | None

@dataclass
class Orp:                  # obec s rozšířenou působností (ORP)
    code: str
    name: str
    district_code: str                  # parent okres
    seat_municipality_code: str | None  # obec that is the ORP seat
    abolished: str | None

@dataclass
class Pou:                  # obec s pověřeným obecním úřadem (POÚ)
    code: str
    name: str
    orp_code: str                       # parent ORP
    seat_municipality_code: str | None  # obec that is the POÚ seat
    abolished: str | None
```

`abolished` is a date string on historical records and `None` on active ones.

### City districts (MOMC), ORP and POÚ

Statutory cities and Prague are subdivided into MOMC units. Okresy are split into
ORP (obce s rozšířenou působností), which are split again into POÚ (obce s
pověřeným obecním úřadem). These three come from the ČÚZK ArcGIS layers rather
than the CSV registries, so each record already carries its parent code:

```python
from ruian_import import fetch_city_districts, fetch_orps, fetch_pous

for cd in fetch_city_districts():
    print(cd.code, cd.name, "->", cd.municipality_code)

for orp in fetch_orps():
    print(orp.code, orp.name, "-> okres", orp.district_code)

for pou in fetch_pous():
    print(pou.code, pou.name, "-> ORP", pou.orp_code)
```

### Senate electoral districts (senátní volební obvody)

The 81 senate electoral districts are defined by law, not by the RÚIAN
territorial registry, so they are **not** in the ČÚZK CSV registries or ArcGIS.
They come instead from the Czech Statistical Office (ČSÚ) elections open data:

```python
from ruian_import import fetch_senate_districts, fetch_municipality_senate_districts

for district in fetch_senate_districts():
    print(district.number, district.name)   # 1..81, e.g. 1 "Karlovy Vary"

for m in fetch_municipality_senate_districts():
    print(m.municipality_code, m.municipality_name, "-> obvod", m.district_number)
```

`municipality_code` is the RÚIAN code of the obec / městská část / městský obvod
where voting happens — it matches `Municipality.code` for ordinary obce and a
`CityDistrict` code for the split statutory cities.

A regular Senate election renews only a third of the districts, so no single
election covers every municipality. `fetch_municipality_senate_districts()`
therefore returns, for each municipality, its assignment from the **most recent**
election (pass `latest_only=False` for the full history). A handful of large
cities are genuinely divided across several districts (e.g. Praha 4), so a
municipality may appear in more than one district.

Both functions download a dated code-list ZIP whose name changes on each ČSÚ
republish; the current one is discovered automatically via
`resolve_senate_codelist_url()`. Pass an explicit `url=` to pin a specific file.

### Boundary geometry

Each administrative level has a `fetch_*_geometries()` function returning the
full boundary of every unit as GeoJSON, keyed by RÚIAN code, in WGS84 (lon/lat
order):

```python
from ruian_import import fetch_districts, fetch_district_geometries

geoms = fetch_district_geometries()   # {code: {"type": "Polygon"|"MultiPolygon", ...}}

for district in fetch_districts():
    outline = geoms.get(district.code)   # None for abolished units (see below)
    if outline:
        ...
```

The other levels work the same way: `fetch_region_geometries`,
`fetch_orp_geometries`, `fetch_pou_geometries`, `fetch_municipality_geometries`,
`fetch_city_district_geometries`.

Geometry comes from the ČÚZK ArcGIS REST API with server-side Douglas-Peucker
simplification and pagination. Pass a custom tolerance in degrees to trade size
for detail (default ~0.001, roughly 110 m):

```python
detailed = fetch_municipality_geometries(simplify=0.0002)
```

### Municipality coordinates

`UI_OBEC.csv` has no coordinates, so `fetch_municipalities()` leaves `lat`/`lon`
as `None`. `fetch_municipalities_with_coordinates()` makes a second request to
the ArcGIS API and fills them with boundary centroids (accuracy ~100 m):

```python
from ruian_import import fetch_municipalities_with_coordinates

for municipality in fetch_municipalities_with_coordinates():
    print(municipality.name, municipality.lat, municipality.lon)
```

Or fetch just the centroids as a lookup table:

```python
from ruian_import import fetch_municipality_coordinates

coords = fetch_municipality_coordinates()   # {code: (lat, lon)}
```

### Active vs. abolished units

The registries carry historical records alongside active ones, and the sources
don't agree on what they include:

- `fetch_regions()`, `fetch_districts()`, `fetch_municipalities()`,
  `fetch_city_districts()`, `fetch_orps()` and `fetch_pous()` include abolished
  units. Filter on `abolished is None` if you only want active ones.
- The geometry functions and `fetch_municipality_coordinates()` return **active
  units only**.

So an abolished unit has a record but no geometry. Look geometry up with
`geoms.get(code)` rather than `geoms[code]` unless you've already filtered to
active units.

### Command line

The package installs a `ruian-import` command (also `python -m ruian_import`)
that writes any level to stdout as JSON:

```bash
ruian-import regions
ruian-import municipalities --coordinates > obce.json
ruian-import district-geometries --simplify 0.0002
```

Datasets: `regions`, `districts`, `municipalities`, `city-districts`, `orps`,
`pous`, `senate-districts`, `municipality-senate-districts`, and
`<level>-geometries` for each level.

### Parsing local files

Every `fetch_*` for the CSV registries is a download wrapped around a parser you
can call directly on bytes you already have:

```python
from pathlib import Path
from ruian_import import parse_municipalities

data = Path("UI_OBEC.zip").read_bytes()
municipalities = list(parse_municipalities(data))
```

`parse_regions` and `parse_districts` work the same way. `download(url)` is also
exported if you want the registry bytes without parsing.

### Errors

Downloads retry twice on network failure and then raise
`requests.RequestException`. Nothing is cached, so repeated calls re-download.

## Data sources

The ČÚZK registries are updated daily:

- Regions: `https://services.cuzk.cz/sestavy/cis/UI_VUSC.zip`
- Districts: `https://services.cuzk.cz/sestavy/cis/UI_OKRES.zip`
- Municipalities: `https://services.cuzk.cz/sestavy/cis/UI_OBEC.zip`

City districts, ORP, POÚ and all geometry come from the ČÚZK ArcGIS MapServer at
`https://ags.cuzk.cz/arcgis/rest/services/RUIAN/MapServer`.

Senate electoral districts come from the ČSÚ elections open data at
`https://volby.gov.cz/opendata/senat_vse/` (code lists `secobv.csv` and
`secoco.csv` inside the dated `SENATciselniky…_csv.zip`).

## License

MIT
</content>
</invoke>
