ruian-import 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.egg-info/
4
+ dist/
5
+ build/
6
+ .venv/
7
+ *.egg
@@ -0,0 +1,116 @@
1
+ Metadata-Version: 2.4
2
+ Name: ruian-import
3
+ Version: 0.1.0
4
+ Summary: Download and parse Czech RUIAN (ČÚZK) data: municipalities, districts, and regions
5
+ Project-URL: Repository, https://gitlab.com/alexandra.tapkova/ruian-import
6
+ Author-email: Alexandra Ťapková <git@ouppy.space>
7
+ License: MIT
8
+ Keywords: cuzk,czech,gis,municipalities,ruian
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Software Development :: Libraries
17
+ Requires-Python: >=3.10
18
+ Requires-Dist: requests>=2.28
19
+ Provides-Extra: coordinates
20
+ Requires-Dist: pyproj>=3.4; extra == 'coordinates'
21
+ Provides-Extra: dev
22
+ Requires-Dist: pyproj>=3.4; extra == 'dev'
23
+ Requires-Dist: pytest; extra == 'dev'
24
+ Requires-Dist: responses; extra == 'dev'
25
+ Description-Content-Type: text/markdown
26
+
27
+ # ruian-import
28
+
29
+ Python knihovna pro stahování a parsování číselníků ČÚZK (RÚIAN): kraje, okresy a obce.
30
+
31
+ ## Instalace
32
+
33
+ ```bash
34
+ pip install ruian-import
35
+ # s podporou souřadnic (JTSK -> WGS84):
36
+ pip install "ruian-import[coordinates]"
37
+ ```
38
+
39
+ ## Použití
40
+
41
+ ```python
42
+ from ruian_import import fetch_kraje, fetch_okresy, fetch_obce
43
+
44
+ kraje = fetch_kraje() # list[Kraj]
45
+ okresy = fetch_okresy() # list[Okres]
46
+ obce = fetch_obce() # list[Obec]
47
+ ```
48
+
49
+ ### Datové třídy
50
+
51
+ ```python
52
+ @dataclass
53
+ class Kraj:
54
+ kod: str # např. "19"
55
+ nazev: str # např. "Hlavní město Praha"
56
+ nuts_lau: str | None # např. "CZ010"
57
+ zrusen: str | None # datum zrušení "YYYY-MM-DD" nebo None
58
+
59
+ @dataclass
60
+ class Okres:
61
+ kod: str
62
+ nazev: str
63
+ kraj_kod: str
64
+ nuts_lau: str | None
65
+ zrusen: str | None
66
+
67
+ @dataclass
68
+ class Obec:
69
+ kod: str # 6místný kód obce, např. "554979"
70
+ nazev: str
71
+ okres_kod: str
72
+ lat: float | None # WGS84, dostupné jen s [coordinates]
73
+ lon: float | None
74
+ zrusen: str | None
75
+ ```
76
+
77
+ Zrušené záznamy mají vyplněné pole `zrusen`; platné záznamy mají `zrusen = None`.
78
+
79
+ ### Souřadnice obcí
80
+
81
+ `UI_OBEC.csv` souřadnice neobsahuje. Pro doplnění souřadnic použij `fetch_obce_with_coordinates()`, které provede druhý dotaz na ArcGIS REST API ČÚZK a vrátí centroidy polygonů (přesnost ~100 m, jeden HTTP request pro všechny obce):
82
+
83
+ ```python
84
+ from ruian_import import fetch_obce_with_coordinates
85
+
86
+ obce = fetch_obce_with_coordinates() # lat/lon vyplněné
87
+ ```
88
+
89
+ Nebo načti souřadnice samostatně:
90
+
91
+ ```python
92
+ from ruian_import import fetch_obec_coordinates
93
+
94
+ coords = fetch_obec_coordinates() # dict[str, tuple[float, float]] - kod -> (lat, lon)
95
+ ```
96
+
97
+ ### Vlastní URL / lokální soubory
98
+
99
+ ```python
100
+ from ruian_import._parse import parse_obce
101
+
102
+ data = Path("UI_OBEC.zip").read_bytes()
103
+ obce = parse_obce(data)
104
+ ```
105
+
106
+ ## Zdroje dat
107
+
108
+ Číselníky ČÚZK se aktualizují denně:
109
+
110
+ - **Kraje**: `https://services.cuzk.cz/sestavy/cis/UI_VUSC.zip`
111
+ - **Okresy**: `https://services.cuzk.cz/sestavy/cis/UI_OKRES.zip`
112
+ - **Obce**: `https://services.cuzk.cz/sestavy/cis/UI_OBEC.zip`
113
+
114
+ ## Licence
115
+
116
+ MIT
@@ -0,0 +1,90 @@
1
+ # ruian-import
2
+
3
+ Python knihovna pro stahování a parsování číselníků ČÚZK (RÚIAN): kraje, okresy a obce.
4
+
5
+ ## Instalace
6
+
7
+ ```bash
8
+ pip install ruian-import
9
+ # s podporou souřadnic (JTSK -> WGS84):
10
+ pip install "ruian-import[coordinates]"
11
+ ```
12
+
13
+ ## Použití
14
+
15
+ ```python
16
+ from ruian_import import fetch_kraje, fetch_okresy, fetch_obce
17
+
18
+ kraje = fetch_kraje() # list[Kraj]
19
+ okresy = fetch_okresy() # list[Okres]
20
+ obce = fetch_obce() # list[Obec]
21
+ ```
22
+
23
+ ### Datové třídy
24
+
25
+ ```python
26
+ @dataclass
27
+ class Kraj:
28
+ kod: str # např. "19"
29
+ nazev: str # např. "Hlavní město Praha"
30
+ nuts_lau: str | None # např. "CZ010"
31
+ zrusen: str | None # datum zrušení "YYYY-MM-DD" nebo None
32
+
33
+ @dataclass
34
+ class Okres:
35
+ kod: str
36
+ nazev: str
37
+ kraj_kod: str
38
+ nuts_lau: str | None
39
+ zrusen: str | None
40
+
41
+ @dataclass
42
+ class Obec:
43
+ kod: str # 6místný kód obce, např. "554979"
44
+ nazev: str
45
+ okres_kod: str
46
+ lat: float | None # WGS84, dostupné jen s [coordinates]
47
+ lon: float | None
48
+ zrusen: str | None
49
+ ```
50
+
51
+ Zrušené záznamy mají vyplněné pole `zrusen`; platné záznamy mají `zrusen = None`.
52
+
53
+ ### Souřadnice obcí
54
+
55
+ `UI_OBEC.csv` souřadnice neobsahuje. Pro doplnění souřadnic použij `fetch_obce_with_coordinates()`, které provede druhý dotaz na ArcGIS REST API ČÚZK a vrátí centroidy polygonů (přesnost ~100 m, jeden HTTP request pro všechny obce):
56
+
57
+ ```python
58
+ from ruian_import import fetch_obce_with_coordinates
59
+
60
+ obce = fetch_obce_with_coordinates() # lat/lon vyplněné
61
+ ```
62
+
63
+ Nebo načti souřadnice samostatně:
64
+
65
+ ```python
66
+ from ruian_import import fetch_obec_coordinates
67
+
68
+ coords = fetch_obec_coordinates() # dict[str, tuple[float, float]] - kod -> (lat, lon)
69
+ ```
70
+
71
+ ### Vlastní URL / lokální soubory
72
+
73
+ ```python
74
+ from ruian_import._parse import parse_obce
75
+
76
+ data = Path("UI_OBEC.zip").read_bytes()
77
+ obce = parse_obce(data)
78
+ ```
79
+
80
+ ## Zdroje dat
81
+
82
+ Číselníky ČÚZK se aktualizují denně:
83
+
84
+ - **Kraje**: `https://services.cuzk.cz/sestavy/cis/UI_VUSC.zip`
85
+ - **Okresy**: `https://services.cuzk.cz/sestavy/cis/UI_OKRES.zip`
86
+ - **Obce**: `https://services.cuzk.cz/sestavy/cis/UI_OBEC.zip`
87
+
88
+ ## Licence
89
+
90
+ MIT
@@ -0,0 +1,39 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "ruian-import"
7
+ version = "0.1.0"
8
+ description = "Download and parse Czech RUIAN (ČÚZK) data: municipalities, districts, and regions"
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ requires-python = ">=3.10"
12
+ authors = [{ name = "Alexandra Ťapková", email = "git@ouppy.space" }]
13
+ keywords = ["ruian", "cuzk", "czech", "municipalities", "gis"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Topic :: Software Development :: Libraries",
23
+ ]
24
+
25
+ dependencies = ["requests>=2.28"]
26
+
27
+ [project.optional-dependencies]
28
+ coordinates = ["pyproj>=3.4"]
29
+ dev = ["pytest", "responses", "pyproj>=3.4"]
30
+
31
+ [project.urls]
32
+ Repository = "https://gitlab.com/alexandra.tapkova/ruian-import"
33
+
34
+ [tool.hatch.build.targets.wheel]
35
+ packages = ["src/ruian_import"]
36
+
37
+ [tool.pytest.ini_options]
38
+ testpaths = ["tests"]
39
+ markers = ["network: requires internet access"]
@@ -0,0 +1,67 @@
1
+ """
2
+ ruian-import: stahování a parsování číselníků ČÚZK (RÚIAN).
3
+
4
+ Základní použití::
5
+
6
+ from ruian_import import fetch_kraje, fetch_okresy, fetch_obce
7
+
8
+ kraje = fetch_kraje()
9
+ okresy = fetch_okresy()
10
+ obce = fetch_obce()
11
+
12
+ Každá funkce vrátí seznam dataclass objektů (Kraj, Okres, Obec).
13
+ Zrušené záznamy mají vyplněné pole ``zrusen`` (datum jako řetězec).
14
+
15
+ Geolokace obcí (lat/lon) je k dispozici jen pokud je nainstalován
16
+ volitelný balíček ``pyproj`` (``pip install ruian-import[coordinates]``).
17
+ """
18
+
19
+ from ._arcgis import fetch_obec_coordinates
20
+ from ._http import download
21
+ from ._models import Kraj, Obec, Okres
22
+ from ._parse import OBCE_URL, OKRESY_URL, KRAJE_URL, parse_kraje, parse_obce, parse_okresy
23
+
24
+ __all__ = [
25
+ "Kraj",
26
+ "Okres",
27
+ "Obec",
28
+ "fetch_kraje",
29
+ "fetch_okresy",
30
+ "fetch_obce",
31
+ "fetch_obec_coordinates",
32
+ "KRAJE_URL",
33
+ "OKRESY_URL",
34
+ "OBCE_URL",
35
+ ]
36
+
37
+
38
+ def fetch_kraje(url: str = KRAJE_URL) -> list[Kraj]:
39
+ """Stáhne a naparsuje číselník krajů z ČÚZK."""
40
+ return parse_kraje(download(url))
41
+
42
+
43
+ def fetch_okresy(url: str = OKRESY_URL) -> list[Okres]:
44
+ """Stáhne a naparsuje číselník okresů z ČÚZK."""
45
+ return parse_okresy(download(url))
46
+
47
+
48
+ def fetch_obce(url: str = OBCE_URL) -> list[Obec]:
49
+ """Stáhne a naparsuje číselník obcí z ČÚZK. Pole lat/lon bude None."""
50
+ return parse_obce(download(url))
51
+
52
+
53
+ def fetch_obce_with_coordinates(
54
+ obce_url: str = OBCE_URL,
55
+ ) -> list[Obec]:
56
+ """
57
+ Stáhne číselník obcí i jejich souřadnice (WGS84) a vrátí je sloučené.
58
+
59
+ Provede dva HTTP dotazy: jeden na ČÚZK číselník, jeden na ArcGIS REST API.
60
+ Souřadnice jsou centroidy polygonů územních celků (přesnost ~100 m).
61
+ """
62
+ obce = parse_obce(download(obce_url))
63
+ coords = fetch_obec_coordinates()
64
+ for obec in obce:
65
+ if obec.kod in coords:
66
+ obec.lat, obec.lon = coords[obec.kod]
67
+ return obce
@@ -0,0 +1,50 @@
1
+ """
2
+ Stahování souřadnic obcí z ArcGIS REST API ČÚZK.
3
+
4
+ Endpoint vrací polygony územních celků ve WGS84; centroid se počítá průměrem
5
+ vrcholů vnějšího prstence (dostatečná přesnost pro polohový bod obce).
6
+
7
+ URL: https://ags.cuzk.cz/arcgis/rest/services/RUIAN/MapServer/12
8
+ """
9
+
10
+ import json
11
+ from typing import Optional
12
+
13
+ from ._http import download
14
+
15
+ _QUERY_URL = (
16
+ "https://ags.cuzk.cz/arcgis/rest/services/RUIAN/MapServer/12/query"
17
+ "?where=platido+IS+NULL"
18
+ "&outFields=kod"
19
+ "&returnGeometry=true"
20
+ "&geometryPrecision=5"
21
+ "&outSR=4326"
22
+ "&f=json"
23
+ )
24
+
25
+
26
+ def _ring_centroid(ring: list[list[float]]) -> tuple[float, float]:
27
+ """Průměr souřadnic vrcholů vnějšího prstence polygonu."""
28
+ lons = [pt[0] for pt in ring]
29
+ lats = [pt[1] for pt in ring]
30
+ return sum(lats) / len(lats), sum(lons) / len(lons)
31
+
32
+
33
+ def fetch_obec_coordinates(
34
+ url: str = _QUERY_URL,
35
+ ) -> dict[str, tuple[float, float]]:
36
+ """
37
+ Stáhne souřadnice (WGS84) všech platných obcí z ArcGIS REST API ČÚZK.
38
+
39
+ Vrací slovník ``{kod_obce: (lat, lon)}``.
40
+ Jeden HTTP dotaz - vrací ~6 200 obcí najednou.
41
+ """
42
+ data = json.loads(download(url))
43
+ result: dict[str, tuple[float, float]] = {}
44
+ for feature in data.get("features", []):
45
+ kod = str(feature.get("attributes", {}).get("kod", "")).strip()
46
+ rings = (feature.get("geometry") or {}).get("rings")
47
+ if not kod or not rings or not rings[0]:
48
+ continue
49
+ result[kod] = _ring_centroid(rings[0])
50
+ return result
@@ -0,0 +1,45 @@
1
+ """
2
+ Převod S-JTSK (EPSG:5514) na WGS84 (EPSG:4326).
3
+
4
+ Vyžaduje volitelnou závislost pyproj; pokud není nainstalována, funkce
5
+ vrací None a koordináty se vynechají.
6
+ """
7
+
8
+ from typing import Optional
9
+
10
+ try:
11
+ from pyproj import Transformer as _Transformer
12
+
13
+ _transformer = _Transformer.from_crs("EPSG:5514", "EPSG:4326", always_xy=True)
14
+ _PYPROJ_AVAILABLE = True
15
+ except ImportError:
16
+ _PYPROJ_AVAILABLE = False
17
+
18
+
19
+ def _normalize_jtsk(
20
+ first: Optional[float], second: Optional[float]
21
+ ) -> tuple[Optional[float], Optional[float]]:
22
+ """
23
+ Vrátí (X, Y) v S-JTSK. Pořadí sloupců souřadnic není spolehlivé,
24
+ ale rozsahy os se v ČR nepřekrývají (X ⊂ ⟨935, 1230⟩ km, Y ⊂ ⟨430, 905⟩ km).
25
+ """
26
+ if first is None or second is None:
27
+ return None, None
28
+ return (first, second) if first > second else (second, first)
29
+
30
+
31
+ def sjtsk_to_wgs84(
32
+ first: Optional[float], second: Optional[float]
33
+ ) -> tuple[Optional[float], Optional[float]]:
34
+ """
35
+ Převede souřadnice S-JTSK (libovolné pořadí) na (lat, lon) WGS84.
36
+ Vrátí (None, None) pokud nejsou vstupní hodnoty nebo pyproj není nainstalován.
37
+ """
38
+ if not _PYPROJ_AVAILABLE:
39
+ return None, None
40
+ jtsk_x, jtsk_y = _normalize_jtsk(first, second)
41
+ if jtsk_x is None or jtsk_y is None:
42
+ return None, None
43
+ # EPSG:5514 používá záporné souřadnice
44
+ lon, lat = _transformer.transform(-jtsk_y, -jtsk_x)
45
+ return lat, lon
@@ -0,0 +1,16 @@
1
+ import requests
2
+
3
+ _TIMEOUT = 30
4
+ _RETRIES = 2
5
+
6
+
7
+ def download(url: str, timeout: int = _TIMEOUT, retries: int = _RETRIES) -> bytes:
8
+ last_error: Exception | None = None
9
+ for _ in range(1 + retries):
10
+ try:
11
+ response = requests.get(url, timeout=timeout)
12
+ response.raise_for_status()
13
+ return response.content
14
+ except requests.RequestException as exc:
15
+ last_error = exc
16
+ raise last_error # type: ignore[misc]
@@ -0,0 +1,32 @@
1
+ from dataclasses import dataclass
2
+ from typing import Optional
3
+
4
+
5
+ @dataclass
6
+ class Kraj:
7
+ kod: str
8
+ nazev: str
9
+ nuts_lau: Optional[str]
10
+ # None = platný, str = datum zrušení "YYYY-MM-DD"
11
+ zrusen: Optional[str]
12
+
13
+
14
+ @dataclass
15
+ class Okres:
16
+ kod: str
17
+ nazev: str
18
+ kraj_kod: str
19
+ nuts_lau: Optional[str]
20
+ zrusen: Optional[str]
21
+
22
+
23
+ @dataclass
24
+ class Obec:
25
+ kod: str
26
+ nazev: str
27
+ okres_kod: str
28
+ # WGS84; None pokud nejsou k dispozici (UI_OBEC.csv souřadnice neobsahuje;
29
+ # lze doplnit z VDP API nebo vlastní tabulky)
30
+ lat: Optional[float]
31
+ lon: Optional[float]
32
+ zrusen: Optional[str]
@@ -0,0 +1,142 @@
1
+ """
2
+ Parsování CSV číselníků ČÚZK.
3
+
4
+ Zdroje (denní aktualizace):
5
+ Kraje: https://services.cuzk.cz/sestavy/cis/UI_VUSC.zip
6
+ Okresy: https://services.cuzk.cz/sestavy/cis/UI_OKRES.zip
7
+ Obce: https://services.cuzk.cz/sestavy/cis/UI_OBEC.zip
8
+
9
+ Formát: ZIP s jedním CSV souborem, oddělovač `;`.
10
+ Kódování: UTF-8 nebo cp1250 (soubory ČÚZK nemají jednotné kódování).
11
+ Sloupce jsou stabilní ale neúplné - UI_OBEC.csv souřadnice neobsahuje.
12
+ """
13
+
14
+ import csv
15
+ import io
16
+ import zipfile
17
+ from typing import Optional
18
+
19
+ from ._coordinates import sjtsk_to_wgs84
20
+ from ._models import Kraj, Obec, Okres
21
+
22
+ KRAJE_URL = "https://services.cuzk.cz/sestavy/cis/UI_VUSC.zip"
23
+ OKRESY_URL = "https://services.cuzk.cz/sestavy/cis/UI_OKRES.zip"
24
+ OBCE_URL = "https://services.cuzk.cz/sestavy/cis/UI_OBEC.zip"
25
+
26
+
27
+ def decode_csv(data: bytes) -> str:
28
+ """Dekóduje bytes ze ČÚZK; zkouší UTF-8, padne zpět na cp1250."""
29
+ try:
30
+ text = data.decode("utf-8")
31
+ except UnicodeDecodeError:
32
+ text = data.decode("cp1250")
33
+ return text.lstrip("")
34
+
35
+
36
+ def read_zipped_csvs(data: bytes) -> list[str]:
37
+ with zipfile.ZipFile(io.BytesIO(data)) as zf:
38
+ return [
39
+ decode_csv(zf.read(name))
40
+ for name in zf.namelist()
41
+ if name.lower().endswith(".csv")
42
+ ]
43
+
44
+
45
+ def _parse_float(value: Optional[str]) -> Optional[float]:
46
+ value = (value or "").strip().replace(",", ".")
47
+ if not value:
48
+ return None
49
+ try:
50
+ return float(value)
51
+ except ValueError:
52
+ return None
53
+
54
+
55
+ def _plati_do(row: dict) -> Optional[str]:
56
+ """Vrátí datum zrušení jako 'YYYY-MM-DD' nebo None (stále platný)."""
57
+ raw = (row.get("PLATI_DO") or "").strip()
58
+ if not raw:
59
+ return None
60
+ return raw.split(" ")[0]
61
+
62
+
63
+ def parse_kraje(data: bytes) -> list[Kraj]:
64
+ """
65
+ Sloupce UI_VUSC.csv:
66
+ KOD;NAZEV;REGSOUDR_KOD;NUTS_LAU;PLATI_OD;PLATI_DO;DATUM_VZNIKU
67
+ """
68
+ result: list[Kraj] = []
69
+ for text in read_zipped_csvs(data):
70
+ for row in csv.DictReader(io.StringIO(text), delimiter=";"):
71
+ kod = (row.get("KOD") or "").strip()
72
+ nazev = (row.get("NAZEV") or "").strip()
73
+ if not kod or not nazev:
74
+ continue
75
+ result.append(
76
+ Kraj(
77
+ kod=kod,
78
+ nazev=nazev,
79
+ nuts_lau=(row.get("NUTS_LAU") or "").strip() or None,
80
+ zrusen=_plati_do(row),
81
+ )
82
+ )
83
+ return result
84
+
85
+
86
+ def parse_okresy(data: bytes) -> list[Okres]:
87
+ """
88
+ Sloupce UI_OKRES.csv:
89
+ KOD;NAZEV;VUSC_KOD;NUTS_LAU;PLATI_OD;PLATI_DO;DATUM_VZNIKU
90
+ """
91
+ result: list[Okres] = []
92
+ for text in read_zipped_csvs(data):
93
+ for row in csv.DictReader(io.StringIO(text), delimiter=";"):
94
+ kod = (row.get("KOD") or "").strip()
95
+ nazev = (row.get("NAZEV") or "").strip()
96
+ kraj_kod = (row.get("VUSC_KOD") or "").strip()
97
+ if not kod or not nazev or not kraj_kod:
98
+ continue
99
+ result.append(
100
+ Okres(
101
+ kod=kod,
102
+ nazev=nazev,
103
+ kraj_kod=kraj_kod,
104
+ nuts_lau=(row.get("NUTS_LAU") or "").strip() or None,
105
+ zrusen=_plati_do(row),
106
+ )
107
+ )
108
+ return result
109
+
110
+
111
+ def parse_obce(data: bytes) -> list[Obec]:
112
+ """
113
+ Sloupce UI_OBEC.csv:
114
+ KOD;NAZEV;STATUS_KOD;POU_KOD;OKRES_KOD;CLENENI_SM_ROZSAH_KOD;CLENENI_SM_TYP_KOD;PLATI_OD;PLATI_DO;DATUM_VZNIKU
115
+
116
+ Souřadnice v tomto CSV nejsou; lat/lon bude None pokud nejsou dodány externě.
117
+ Pro doplnění souřadnic použij fetch_obec_coordinates() nebo vlastní tabulku.
118
+ """
119
+ result: list[Obec] = []
120
+ for text in read_zipped_csvs(data):
121
+ for row in csv.DictReader(io.StringIO(text), delimiter=";"):
122
+ kod = (row.get("KOD") or "").strip()
123
+ nazev = (row.get("NAZEV") or "").strip()
124
+ okres_kod = (row.get("OKRES_KOD") or "").strip()
125
+ if not kod or not nazev or not okres_kod:
126
+ continue
127
+ # Pokud by budoucí export souřadnice přidal, zpracujeme je zde
128
+ lat, lon = sjtsk_to_wgs84(
129
+ _parse_float(row.get("SOURADNICE_X")),
130
+ _parse_float(row.get("SOURADNICE_Y")),
131
+ )
132
+ result.append(
133
+ Obec(
134
+ kod=kod,
135
+ nazev=nazev,
136
+ okres_kod=okres_kod,
137
+ lat=lat,
138
+ lon=lon,
139
+ zrusen=_plati_do(row),
140
+ )
141
+ )
142
+ return result
File without changes
@@ -0,0 +1,75 @@
1
+ from pathlib import Path
2
+
3
+ import pytest
4
+
5
+ from ruian_import._arcgis import fetch_obec_coordinates
6
+ from ruian_import._parse import parse_kraje, parse_obce, parse_okresy
7
+
8
+ FIXTURES = Path(__file__).parent / "fixtures"
9
+
10
+
11
+ def test_parse_kraje():
12
+ data = (FIXTURES / "UI_VUSC.zip").read_bytes()
13
+ kraje = parse_kraje(data)
14
+ assert len(kraje) > 0
15
+ kody = {k.kod for k in kraje}
16
+ assert "19" in kody # Hlavní město Praha
17
+ for k in kraje:
18
+ assert k.kod
19
+ assert k.nazev
20
+ # platné kraje nemají datum zrušení
21
+ platne = [k for k in kraje if k.zrusen is None]
22
+ assert len(platne) > 0
23
+
24
+
25
+ def test_parse_okresy():
26
+ data = (FIXTURES / "UI_OKRES.zip").read_bytes()
27
+ okresy = parse_okresy(data)
28
+ assert len(okresy) > 0
29
+ for o in okresy:
30
+ assert o.kod
31
+ assert o.nazev
32
+ assert o.kraj_kod
33
+
34
+
35
+ def test_parse_obce():
36
+ data = (FIXTURES / "UI_OBEC.zip").read_bytes()
37
+ obce = parse_obce(data)
38
+ assert len(obce) > 5000 # ČR má ~6 200 obcí
39
+ for o in obce:
40
+ assert o.kod
41
+ assert o.nazev
42
+ assert o.okres_kod
43
+
44
+
45
+ def test_kraj_nuts():
46
+ data = (FIXTURES / "UI_VUSC.zip").read_bytes()
47
+ kraje = parse_kraje(data)
48
+ nuts = {k.nuts_lau for k in kraje if k.nuts_lau}
49
+ assert any(n.startswith("CZ") for n in nuts)
50
+
51
+
52
+ def test_okres_kraj_vazba():
53
+ kraje_data = (FIXTURES / "UI_VUSC.zip").read_bytes()
54
+ okresy_data = (FIXTURES / "UI_OKRES.zip").read_bytes()
55
+ kraj_kody = {k.kod for k in parse_kraje(kraje_data)}
56
+ for okres in parse_okresy(okresy_data):
57
+ assert okres.kraj_kod in kraj_kody, f"Okres {okres.nazev} má neznámý kraj_kod {okres.kraj_kod}"
58
+
59
+
60
+ def test_obec_okres_vazba():
61
+ okresy_data = (FIXTURES / "UI_OKRES.zip").read_bytes()
62
+ obce_data = (FIXTURES / "UI_OBEC.zip").read_bytes()
63
+ okres_kody = {o.kod for o in parse_okresy(okresy_data)}
64
+ for obec in parse_obce(obce_data):
65
+ assert obec.okres_kod in okres_kody, f"Obec {obec.nazev} má neznámý okres_kod {obec.okres_kod}"
66
+
67
+
68
+ @pytest.mark.network
69
+ def test_fetch_obec_coordinates():
70
+ coords = fetch_obec_coordinates()
71
+ assert len(coords) > 5000
72
+ # Abertamy - kód 554979
73
+ lat, lon = coords["554979"]
74
+ assert 49 < lat < 51, f"Neočekávaná šířka: {lat}"
75
+ assert 12 < lon < 19, f"Neočekávaná délka: {lon}"