duva 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.
- duva-0.1.0/PKG-INFO +5 -0
- duva-0.1.0/README.md +39 -0
- duva-0.1.0/duva/Kommun_Sweref99TM.DAT +0 -0
- duva-0.1.0/duva/Kommun_Sweref99TM.ID +0 -0
- duva-0.1.0/duva/Kommun_Sweref99TM.IND +0 -0
- duva-0.1.0/duva/Kommun_Sweref99TM.MAP +0 -0
- duva-0.1.0/duva/Kommun_Sweref99TM.TAB +15 -0
- duva-0.1.0/duva/RegSO_2025.gpkg +0 -0
- duva-0.1.0/duva/__init__.py +222 -0
- duva-0.1.0/duva.egg-info/PKG-INFO +5 -0
- duva-0.1.0/duva.egg-info/SOURCES.txt +18 -0
- duva-0.1.0/duva.egg-info/dependency_links.txt +1 -0
- duva-0.1.0/duva.egg-info/requires.txt +1 -0
- duva-0.1.0/duva.egg-info/top_level.txt +1 -0
- duva-0.1.0/pyproject.toml +21 -0
- duva-0.1.0/setup.cfg +4 -0
- duva-0.1.0/tests/test_duva.py +84 -0
- duva-0.1.0/tests/test_duva_df.py +92 -0
- duva-0.1.0/tests/test_duva_from_kod.py +62 -0
- duva-0.1.0/tests/test_duva_many.py +64 -0
duva-0.1.0/PKG-INFO
ADDED
duva-0.1.0/README.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# duva 🕊️
|
|
2
|
+
|
|
3
|
+
Reverse geocode Swedish coordinates to RegSO areas.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install duva
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from duva import duva, duva_many, duva_df, duva_from_kod
|
|
15
|
+
|
|
16
|
+
# Single coordinate (WGS84)
|
|
17
|
+
duva(x=17.88, y=59.35)
|
|
18
|
+
# {'regsonamn': 'Blackeberg', 'kommunnamn': 'Stockholm', 'lansnamn': 'Stockholms län', ...}
|
|
19
|
+
|
|
20
|
+
# As object
|
|
21
|
+
result = duva(x=17.88, y=59.35, as_object=True)
|
|
22
|
+
result.regsonamn # 'Blackeberg'
|
|
23
|
+
|
|
24
|
+
# List of coordinates
|
|
25
|
+
duva_many([(17.88, 59.35), (18.07, 59.28)])
|
|
26
|
+
|
|
27
|
+
# Enrich a Polars DataFrame
|
|
28
|
+
duva_df(df, x_col="lon", y_col="lat")
|
|
29
|
+
|
|
30
|
+
# Lookup by RegSO code
|
|
31
|
+
duva_from_kod("0180R009")
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Notes
|
|
35
|
+
|
|
36
|
+
- Input coordinates must be WGS84 (standard GPS)
|
|
37
|
+
- Raises `ValueError` for invalid coordinates or coordinates outside Sweden
|
|
38
|
+
- Returns `not_on_land=True` for water areas, `is_baltic=True` for international water
|
|
39
|
+
- Based on SCB's RegSO 2025 and municipality boundaries
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
!table
|
|
2
|
+
!version 300
|
|
3
|
+
!charset WindowsLatin1
|
|
4
|
+
|
|
5
|
+
Definition Table
|
|
6
|
+
Type NATIVE Charset "WindowsLatin1"
|
|
7
|
+
Fields 2
|
|
8
|
+
KnKod Char (4) Index 1 ;
|
|
9
|
+
KnNamn Char (15) Index 2 ;
|
|
10
|
+
begin_metadata
|
|
11
|
+
"\MapInfo" = ""
|
|
12
|
+
"\MapInfo\TableID" = "6b951346-0ed0-4fdf-9893-79398f968eb8"
|
|
13
|
+
"\MapInfo\ParentTableID" = "5505f6c7-db00-4b1a-a344-db38bbddbb57"
|
|
14
|
+
"\IsReadOnly" = "FALSE"
|
|
15
|
+
end_metadata
|
|
Binary file
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import geopandas as gpd
|
|
4
|
+
import polars as pl
|
|
5
|
+
from shapely.geometry import Point
|
|
6
|
+
|
|
7
|
+
_GPKG_PATH = Path(__file__).parent / "RegSO_2025.gpkg"
|
|
8
|
+
_KOMMUN_PATH = Path(__file__).parent / "Kommun_Sweref99TM.TAB"
|
|
9
|
+
|
|
10
|
+
_GDF: gpd.GeoDataFrame = gpd.read_file(_GPKG_PATH).drop(columns=["objectid", "geometry"])
|
|
11
|
+
_GDF_GEO: gpd.GeoDataFrame = gpd.read_file(_GPKG_PATH)[["regsokod", "geometry"]]
|
|
12
|
+
_KOMMUN_GEO: gpd.GeoDataFrame = gpd.read_file(_KOMMUN_PATH)[["KnKod", "KnNamn", "geometry"]].set_crs("EPSG:3006", allow_override=True)
|
|
13
|
+
_KOD_INDEX: dict[str, dict] = {
|
|
14
|
+
row["regsokod"]: row
|
|
15
|
+
for row in _GDF.to_dict(orient="records")
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
_LAN: dict[str, str] = {
|
|
19
|
+
"01": "Stockholms län",
|
|
20
|
+
"03": "Uppsala län",
|
|
21
|
+
"04": "Södermanlands län",
|
|
22
|
+
"05": "Östergötlands län",
|
|
23
|
+
"06": "Jönköpings län",
|
|
24
|
+
"07": "Kronobergs län",
|
|
25
|
+
"08": "Kalmar län",
|
|
26
|
+
"09": "Gotlands län",
|
|
27
|
+
"10": "Blekinge län",
|
|
28
|
+
"12": "Skåne län",
|
|
29
|
+
"13": "Hallands län",
|
|
30
|
+
"14": "Västra Götalands län",
|
|
31
|
+
"17": "Värmlands län",
|
|
32
|
+
"18": "Örebro län",
|
|
33
|
+
"19": "Västmanlands län",
|
|
34
|
+
"20": "Dalarnas län",
|
|
35
|
+
"21": "Gävleborgs län",
|
|
36
|
+
"22": "Västernorrlands län",
|
|
37
|
+
"23": "Jämtlands län",
|
|
38
|
+
"24": "Västerbottens län",
|
|
39
|
+
"25": "Norrbottens län",
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
_LON_MIN, _LAT_MIN, _LON_MAX, _LAT_MAX = 10.59, 55.13, 24.18, 69.06
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class RegSO:
|
|
46
|
+
def __init__(
|
|
47
|
+
self,
|
|
48
|
+
not_on_land: bool,
|
|
49
|
+
is_baltic: bool = False,
|
|
50
|
+
objektidentitet: str | None = None,
|
|
51
|
+
objekttyp: str | None = None,
|
|
52
|
+
regsokod: str | None = None,
|
|
53
|
+
regsonamn: str | None = None,
|
|
54
|
+
lanskod: str | None = None,
|
|
55
|
+
lansnamn: str | None = None,
|
|
56
|
+
kommunkod: str | None = None,
|
|
57
|
+
kommunnamn: str | None = None,
|
|
58
|
+
version: str | None = None,
|
|
59
|
+
ansvarig_organisation: str | None = None,
|
|
60
|
+
referensdatum: str | None = None,
|
|
61
|
+
) -> None:
|
|
62
|
+
self.not_on_land = not_on_land
|
|
63
|
+
self.is_baltic = is_baltic
|
|
64
|
+
self.objektidentitet = objektidentitet
|
|
65
|
+
self.objekttyp = objekttyp
|
|
66
|
+
self.regsokod = regsokod
|
|
67
|
+
self.regsonamn = regsonamn
|
|
68
|
+
self.lanskod = lanskod
|
|
69
|
+
self.lansnamn = lansnamn
|
|
70
|
+
self.kommunkod = kommunkod
|
|
71
|
+
self.kommunnamn = kommunnamn
|
|
72
|
+
self.version = version
|
|
73
|
+
self.ansvarig_organisation = ansvarig_organisation
|
|
74
|
+
self.referensdatum = referensdatum
|
|
75
|
+
|
|
76
|
+
def __repr__(self) -> str:
|
|
77
|
+
return (
|
|
78
|
+
f"RegSO(regsokod={self.regsokod!r}, regsonamn={self.regsonamn!r}, "
|
|
79
|
+
f"kommunkod={self.kommunkod!r}, kommunnamn={self.kommunnamn!r}, "
|
|
80
|
+
f"lansnamn={self.lansnamn!r}, not_on_land={self.not_on_land}, "
|
|
81
|
+
f"is_baltic={self.is_baltic})"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _validate(x: float, y: float) -> None:
|
|
86
|
+
if not (-180 <= x <= 180):
|
|
87
|
+
raise ValueError(f"Invalid longitude: {x}. Must be between -180 and 180.")
|
|
88
|
+
if not (-90 <= y <= 90):
|
|
89
|
+
raise ValueError(f"Invalid latitude: {y}. Must be between -90 and 90.")
|
|
90
|
+
if not (_LON_MIN <= x <= _LON_MAX and _LAT_MIN <= y <= _LAT_MAX):
|
|
91
|
+
raise ValueError(f"Coordinates ({x}, {y}) are outside Sweden.")
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _lookup_kommun(point_3006: gpd.GeoDataFrame) -> tuple[str | None, str | None]:
|
|
95
|
+
match = _KOMMUN_GEO[_KOMMUN_GEO.contains(point_3006.geometry.iloc[0])]
|
|
96
|
+
if match.empty:
|
|
97
|
+
return None, None
|
|
98
|
+
return match.iloc[0]["KnKod"], match.iloc[0]["KnNamn"]
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _lookup_point(x: float, y: float) -> dict:
|
|
102
|
+
point = gpd.GeoDataFrame(
|
|
103
|
+
geometry=[Point(x, y)],
|
|
104
|
+
crs="EPSG:4326",
|
|
105
|
+
).to_crs("EPSG:3006")
|
|
106
|
+
|
|
107
|
+
match = _GDF_GEO[_GDF_GEO.contains(point.geometry.iloc[0])]
|
|
108
|
+
|
|
109
|
+
if match.empty:
|
|
110
|
+
kommunkod, kommunnamn = _lookup_kommun(point)
|
|
111
|
+
return {
|
|
112
|
+
"not_on_land": True,
|
|
113
|
+
"is_baltic": kommunkod is None,
|
|
114
|
+
"kommunkod": kommunkod,
|
|
115
|
+
"kommunnamn": kommunnamn,
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
row = dict(_KOD_INDEX[match.iloc[0]["regsokod"]])
|
|
119
|
+
km = _KOMMUN_GEO[_KOMMUN_GEO["KnKod"] == row["kommunkod"]]
|
|
120
|
+
row["kommunnamn"] = km.iloc[0]["KnNamn"] if not km.empty else None
|
|
121
|
+
row["lansnamn"] = _LAN.get(row["lanskod"])
|
|
122
|
+
row["not_on_land"] = False
|
|
123
|
+
row["is_baltic"] = False
|
|
124
|
+
return row
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def duva(x: float, y: float, as_object: bool = False) -> dict | RegSO:
|
|
128
|
+
_validate(x, y)
|
|
129
|
+
row = _lookup_point(x, y)
|
|
130
|
+
return RegSO(**row) if as_object else row
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def duva_many(
|
|
134
|
+
coords: list[tuple[float, float]],
|
|
135
|
+
as_object: bool = False,
|
|
136
|
+
) -> list[dict | RegSO]:
|
|
137
|
+
results = []
|
|
138
|
+
for x, y in coords:
|
|
139
|
+
try:
|
|
140
|
+
_validate(x, y)
|
|
141
|
+
row = _lookup_point(x, y)
|
|
142
|
+
except ValueError as e:
|
|
143
|
+
row = {"error": str(e), "not_on_land": None}
|
|
144
|
+
results.append(RegSO(**row) if as_object else row)
|
|
145
|
+
return results
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def duva_df(
|
|
149
|
+
df: pl.DataFrame,
|
|
150
|
+
x_col: str = "lon",
|
|
151
|
+
y_col: str = "lat",
|
|
152
|
+
) -> pl.DataFrame:
|
|
153
|
+
pdf = df.to_pandas()
|
|
154
|
+
valid = pdf[x_col].notna() & pdf[y_col].notna()
|
|
155
|
+
|
|
156
|
+
gdf = gpd.GeoDataFrame(
|
|
157
|
+
pdf[valid][[x_col, y_col]].copy(),
|
|
158
|
+
geometry=gpd.points_from_xy(
|
|
159
|
+
pdf.loc[valid, x_col].astype(float),
|
|
160
|
+
pdf.loc[valid, y_col].astype(float),
|
|
161
|
+
),
|
|
162
|
+
crs="EPSG:4326",
|
|
163
|
+
).to_crs("EPSG:3006")
|
|
164
|
+
|
|
165
|
+
joined_regso = gpd.sjoin(gdf, _GDF_GEO, how="left", predicate="within")
|
|
166
|
+
joined_kommun = gpd.sjoin(gdf, _KOMMUN_GEO, how="left", predicate="within")
|
|
167
|
+
|
|
168
|
+
regsokod_list: list[str | None] = [None] * len(pdf)
|
|
169
|
+
regsonamn_list: list[str | None] = [None] * len(pdf)
|
|
170
|
+
kommunkod_list: list[str | None] = [None] * len(pdf)
|
|
171
|
+
kommunnamn_list: list[str | None] = [None] * len(pdf)
|
|
172
|
+
lanskod_list: list[str | None] = [None] * len(pdf)
|
|
173
|
+
lansnamn_list: list[str | None] = [None] * len(pdf)
|
|
174
|
+
not_on_land_list: list[bool] = [False] * len(pdf)
|
|
175
|
+
is_baltic_list: list[bool] = [False] * len(pdf)
|
|
176
|
+
|
|
177
|
+
for idx in pdf[valid].index:
|
|
178
|
+
regso_match = joined_regso[joined_regso.index == idx]
|
|
179
|
+
kommun_match = joined_kommun[joined_kommun.index == idx]
|
|
180
|
+
|
|
181
|
+
kod = regso_match["regsokod"].iloc[0] if len(regso_match) else None
|
|
182
|
+
if kod and not isinstance(kod, float):
|
|
183
|
+
meta = _KOD_INDEX.get(kod, {})
|
|
184
|
+
regsokod_list[idx] = kod
|
|
185
|
+
regsonamn_list[idx] = meta.get("regsonamn")
|
|
186
|
+
lanskod_list[idx] = meta.get("lanskod")
|
|
187
|
+
lansnamn_list[idx] = _LAN.get(meta.get("lanskod", ""))
|
|
188
|
+
kommunkod_list[idx] = meta.get("kommunkod")
|
|
189
|
+
km = _KOMMUN_GEO[_KOMMUN_GEO["KnKod"] == kommunkod_list[idx]]
|
|
190
|
+
kommunnamn_list[idx] = km.iloc[0]["KnNamn"] if not km.empty else None
|
|
191
|
+
else:
|
|
192
|
+
not_on_land_list[idx] = True
|
|
193
|
+
if len(kommun_match):
|
|
194
|
+
kk = kommun_match["KnKod"].iloc[0]
|
|
195
|
+
kn = kommun_match["KnNamn"].iloc[0]
|
|
196
|
+
kommunkod_list[idx] = None if isinstance(kk, float) else kk
|
|
197
|
+
kommunnamn_list[idx] = None if isinstance(kn, float) else kn
|
|
198
|
+
is_baltic_list[idx] = kommunkod_list[idx] is None
|
|
199
|
+
|
|
200
|
+
return df.with_columns([
|
|
201
|
+
pl.Series("regsokod", regsokod_list, dtype=pl.String),
|
|
202
|
+
pl.Series("regsonamn", regsonamn_list, dtype=pl.String),
|
|
203
|
+
pl.Series("kommunkod", kommunkod_list, dtype=pl.String),
|
|
204
|
+
pl.Series("kommunnamn", kommunnamn_list, dtype=pl.String),
|
|
205
|
+
pl.Series("lanskod", lanskod_list, dtype=pl.String),
|
|
206
|
+
pl.Series("lansnamn", lansnamn_list, dtype=pl.String),
|
|
207
|
+
pl.Series("not_on_land", not_on_land_list, dtype=pl.Boolean),
|
|
208
|
+
pl.Series("is_baltic", is_baltic_list, dtype=pl.Boolean),
|
|
209
|
+
])
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def duva_from_kod(kod: str, as_object: bool = False) -> dict | RegSO | None:
|
|
213
|
+
row = _KOD_INDEX.get(kod)
|
|
214
|
+
if row is None:
|
|
215
|
+
return None
|
|
216
|
+
row = dict(row)
|
|
217
|
+
km = _KOMMUN_GEO[_KOMMUN_GEO["KnKod"] == row["kommunkod"]]
|
|
218
|
+
row["kommunnamn"] = km.iloc[0]["KnNamn"] if not km.empty else None
|
|
219
|
+
row["lansnamn"] = _LAN.get(row["lanskod"])
|
|
220
|
+
row["not_on_land"] = False
|
|
221
|
+
row["is_baltic"] = False
|
|
222
|
+
return RegSO(**row) if as_object else row
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
duva/Kommun_Sweref99TM.DAT
|
|
4
|
+
duva/Kommun_Sweref99TM.ID
|
|
5
|
+
duva/Kommun_Sweref99TM.IND
|
|
6
|
+
duva/Kommun_Sweref99TM.MAP
|
|
7
|
+
duva/Kommun_Sweref99TM.TAB
|
|
8
|
+
duva/RegSO_2025.gpkg
|
|
9
|
+
duva/__init__.py
|
|
10
|
+
duva.egg-info/PKG-INFO
|
|
11
|
+
duva.egg-info/SOURCES.txt
|
|
12
|
+
duva.egg-info/dependency_links.txt
|
|
13
|
+
duva.egg-info/requires.txt
|
|
14
|
+
duva.egg-info/top_level.txt
|
|
15
|
+
tests/test_duva.py
|
|
16
|
+
tests/test_duva_df.py
|
|
17
|
+
tests/test_duva_from_kod.py
|
|
18
|
+
tests/test_duva_many.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
geopandas>=1.0.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
duva
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "duva"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
requires-python = ">=3.12"
|
|
5
|
+
dependencies = [
|
|
6
|
+
"geopandas>=1.0.0",
|
|
7
|
+
]
|
|
8
|
+
|
|
9
|
+
[build-system]
|
|
10
|
+
requires = ["setuptools>=68"]
|
|
11
|
+
build-backend = "setuptools.build_meta"
|
|
12
|
+
|
|
13
|
+
[tool.setuptools.packages.find]
|
|
14
|
+
where = ["."]
|
|
15
|
+
include = ["duva*"]
|
|
16
|
+
|
|
17
|
+
[tool.setuptools.package-data]
|
|
18
|
+
duva = ["*.gpkg", "*.TAB", "*.DAT", "*.ID", "*.IND", "*.MAP"]
|
|
19
|
+
|
|
20
|
+
[tool.pytest.ini_options]
|
|
21
|
+
testpaths = ["tests"]
|
duva-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from duva import duva, RegSO
|
|
3
|
+
|
|
4
|
+
BLACKEBERG = (17.88, 59.35)
|
|
5
|
+
GAMLA_ENSKEDE = (18.07, 59.28)
|
|
6
|
+
BALTIC = (20.5, 57.5)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_returns_dict_by_default():
|
|
10
|
+
result = duva(*BLACKEBERG)
|
|
11
|
+
assert isinstance(result, dict)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_returns_regso_object():
|
|
15
|
+
result = duva(*BLACKEBERG, as_object=True)
|
|
16
|
+
assert isinstance(result, RegSO)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_correct_regsonamn():
|
|
20
|
+
result = duva(*BLACKEBERG)
|
|
21
|
+
assert result["regsonamn"] == "Blackeberg"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_correct_kommunkod():
|
|
25
|
+
result = duva(*BLACKEBERG)
|
|
26
|
+
assert result["kommunkod"] == "0180"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_correct_kommunnamn():
|
|
30
|
+
result = duva(*BLACKEBERG)
|
|
31
|
+
assert result["kommunnamn"] == "Stockholm"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_correct_lansnamn():
|
|
35
|
+
result = duva(*BLACKEBERG)
|
|
36
|
+
assert result["lansnamn"] == "Stockholms län"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_not_on_land_false_for_land():
|
|
40
|
+
result = duva(*BLACKEBERG)
|
|
41
|
+
assert result["not_on_land"] is False
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_is_baltic_false_for_land():
|
|
45
|
+
result = duva(*BLACKEBERG)
|
|
46
|
+
assert result["is_baltic"] is False
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_not_on_land_true_for_water():
|
|
50
|
+
result = duva(*BALTIC)
|
|
51
|
+
assert result["not_on_land"] is True
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_is_baltic_true_for_baltic():
|
|
55
|
+
result = duva(*BALTIC)
|
|
56
|
+
assert result["is_baltic"] is True
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_water_has_no_regsonamn():
|
|
60
|
+
result = duva(*BALTIC)
|
|
61
|
+
assert result.get("regsonamn") is None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_object_attributes_match_dict():
|
|
65
|
+
d = duva(*GAMLA_ENSKEDE)
|
|
66
|
+
obj = duva(*GAMLA_ENSKEDE, as_object=True)
|
|
67
|
+
assert obj.regsonamn == d["regsonamn"]
|
|
68
|
+
assert obj.kommunkod == d["kommunkod"]
|
|
69
|
+
assert obj.lansnamn == d["lansnamn"]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_invalid_longitude_raises():
|
|
73
|
+
with pytest.raises(ValueError, match="longitude"):
|
|
74
|
+
duva(x=200.0, y=59.0)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_invalid_latitude_raises():
|
|
78
|
+
with pytest.raises(ValueError, match="latitude"):
|
|
79
|
+
duva(x=17.0, y=100.0)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_outside_sweden_raises():
|
|
83
|
+
with pytest.raises(ValueError, match="outside Sweden"):
|
|
84
|
+
duva(x=10.0, y=50.0)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import polars as pl
|
|
2
|
+
import pytest
|
|
3
|
+
from duva import duva_df
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@pytest.fixture
|
|
7
|
+
def sample_df() -> pl.DataFrame:
|
|
8
|
+
return pl.DataFrame({
|
|
9
|
+
"lon": [17.88, 20.5, 18.07, None],
|
|
10
|
+
"lat": [59.35, 57.5, 59.28, None],
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_returns_polars_dataframe(sample_df: pl.DataFrame):
|
|
15
|
+
result = duva_df(sample_df)
|
|
16
|
+
assert isinstance(result, pl.DataFrame)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_adds_regsonamn_column(sample_df: pl.DataFrame):
|
|
20
|
+
result = duva_df(sample_df)
|
|
21
|
+
assert "regsonamn" in result.columns
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_adds_kommunkod_column(sample_df: pl.DataFrame):
|
|
25
|
+
result = duva_df(sample_df)
|
|
26
|
+
assert "kommunkod" in result.columns
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_adds_kommunnamn_column(sample_df: pl.DataFrame):
|
|
30
|
+
result = duva_df(sample_df)
|
|
31
|
+
assert "kommunnamn" in result.columns
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_adds_lanskod_column(sample_df: pl.DataFrame):
|
|
35
|
+
result = duva_df(sample_df)
|
|
36
|
+
assert "lanskod" in result.columns
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_adds_lansnamn_column(sample_df: pl.DataFrame):
|
|
40
|
+
result = duva_df(sample_df)
|
|
41
|
+
assert "lansnamn" in result.columns
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_adds_not_on_land_column(sample_df: pl.DataFrame):
|
|
45
|
+
result = duva_df(sample_df)
|
|
46
|
+
assert "not_on_land" in result.columns
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_adds_is_baltic_column(sample_df: pl.DataFrame):
|
|
50
|
+
result = duva_df(sample_df)
|
|
51
|
+
assert "is_baltic" in result.columns
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_row_count_unchanged(sample_df: pl.DataFrame):
|
|
55
|
+
result = duva_df(sample_df)
|
|
56
|
+
assert len(result) == len(sample_df)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_correct_regsonamn_for_land(sample_df: pl.DataFrame):
|
|
60
|
+
result = duva_df(sample_df)
|
|
61
|
+
assert result["regsonamn"][0] == "Blackeberg"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_not_on_land_false_for_land(sample_df: pl.DataFrame):
|
|
65
|
+
result = duva_df(sample_df)
|
|
66
|
+
assert result["not_on_land"][0] is False
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_not_on_land_true_for_baltic(sample_df: pl.DataFrame):
|
|
70
|
+
result = duva_df(sample_df)
|
|
71
|
+
assert result["not_on_land"][1] is True
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_is_baltic_true_for_baltic(sample_df: pl.DataFrame):
|
|
75
|
+
result = duva_df(sample_df)
|
|
76
|
+
assert result["is_baltic"][1] is True
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_null_coords_produce_null_regso(sample_df: pl.DataFrame):
|
|
80
|
+
result = duva_df(sample_df)
|
|
81
|
+
assert result["regsonamn"][3] is None
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_custom_column_names():
|
|
85
|
+
df = pl.DataFrame({"x": [17.88], "y": [59.35]})
|
|
86
|
+
result = duva_df(df, x_col="x", y_col="y")
|
|
87
|
+
assert result["regsonamn"][0] == "Blackeberg"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def test_lansnamn_correct(sample_df: pl.DataFrame):
|
|
91
|
+
result = duva_df(sample_df)
|
|
92
|
+
assert result["lansnamn"][0] == "Stockholms län"
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from duva import duva_from_kod, RegSO
|
|
2
|
+
|
|
3
|
+
BLACKEBERG_KOD = "0180R009"
|
|
4
|
+
INVALID_KOD = "INVALID"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_returns_dict_by_default():
|
|
8
|
+
result = duva_from_kod(BLACKEBERG_KOD)
|
|
9
|
+
assert isinstance(result, dict)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_returns_regso_object():
|
|
13
|
+
result = duva_from_kod(BLACKEBERG_KOD, as_object=True)
|
|
14
|
+
assert isinstance(result, RegSO)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_correct_regsonamn():
|
|
18
|
+
result = duva_from_kod(BLACKEBERG_KOD)
|
|
19
|
+
assert result["regsonamn"] == "Blackeberg"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_correct_kommunkod():
|
|
23
|
+
result = duva_from_kod(BLACKEBERG_KOD)
|
|
24
|
+
assert result["kommunkod"] == "0180"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_correct_kommunnamn():
|
|
28
|
+
result = duva_from_kod(BLACKEBERG_KOD)
|
|
29
|
+
assert result["kommunnamn"] == "Stockholm"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_correct_lansnamn():
|
|
33
|
+
result = duva_from_kod(BLACKEBERG_KOD)
|
|
34
|
+
assert result["lansnamn"] == "Stockholms län"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_not_on_land_false():
|
|
38
|
+
result = duva_from_kod(BLACKEBERG_KOD)
|
|
39
|
+
assert result["not_on_land"] is False
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_is_baltic_false():
|
|
43
|
+
result = duva_from_kod(BLACKEBERG_KOD)
|
|
44
|
+
assert result["is_baltic"] is False
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_invalid_kod_returns_none():
|
|
48
|
+
result = duva_from_kod(INVALID_KOD)
|
|
49
|
+
assert result is None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_invalid_kod_as_object_returns_none():
|
|
53
|
+
result = duva_from_kod(INVALID_KOD, as_object=True)
|
|
54
|
+
assert result is None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_object_attributes_match_dict():
|
|
58
|
+
d = duva_from_kod(BLACKEBERG_KOD)
|
|
59
|
+
obj = duva_from_kod(BLACKEBERG_KOD, as_object=True)
|
|
60
|
+
assert obj.regsonamn == d["regsonamn"]
|
|
61
|
+
assert obj.kommunkod == d["kommunkod"]
|
|
62
|
+
assert obj.lansnamn == d["lansnamn"]
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from duva import duva_many, RegSO
|
|
2
|
+
|
|
3
|
+
BLACKEBERG = (17.88, 59.35)
|
|
4
|
+
BALTIC = (20.5, 57.5)
|
|
5
|
+
OUTSIDE_SWEDEN = (10.0, 50.0)
|
|
6
|
+
INVALID_LON = (200.0, 59.0)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_returns_list():
|
|
10
|
+
result = duva_many([BLACKEBERG])
|
|
11
|
+
assert isinstance(result, list)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_returns_correct_length():
|
|
15
|
+
coords = [BLACKEBERG, BALTIC, OUTSIDE_SWEDEN]
|
|
16
|
+
result = duva_many(coords)
|
|
17
|
+
assert len(result) == 3
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_returns_dicts_by_default():
|
|
21
|
+
result = duva_many([BLACKEBERG, BALTIC])
|
|
22
|
+
assert all(isinstance(r, dict) for r in result)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_returns_regso_objects():
|
|
26
|
+
result = duva_many([BLACKEBERG, BALTIC], as_object=True)
|
|
27
|
+
assert all(isinstance(r, RegSO) for r in result)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_correct_result_for_land():
|
|
31
|
+
result = duva_many([BLACKEBERG])
|
|
32
|
+
assert result[0]["regsonamn"] == "Blackeberg"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_water_returns_not_on_land():
|
|
36
|
+
result = duva_many([BALTIC])
|
|
37
|
+
assert result[0]["not_on_land"] is True
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_invalid_coords_returns_error_key():
|
|
41
|
+
result = duva_many([OUTSIDE_SWEDEN])
|
|
42
|
+
assert "error" in result[0]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_invalid_coords_not_on_land_is_none():
|
|
46
|
+
result = duva_many([OUTSIDE_SWEDEN])
|
|
47
|
+
assert result[0]["not_on_land"] is None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_mixed_coords():
|
|
51
|
+
result = duva_many([BLACKEBERG, BALTIC, OUTSIDE_SWEDEN])
|
|
52
|
+
assert result[0]["regsonamn"] == "Blackeberg"
|
|
53
|
+
assert result[1]["not_on_land"] is True
|
|
54
|
+
assert "error" in result[2]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_empty_list():
|
|
58
|
+
result = duva_many([])
|
|
59
|
+
assert result == []
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_invalid_lon_returns_error():
|
|
63
|
+
result = duva_many([INVALID_LON])
|
|
64
|
+
assert "error" in result[0]
|