QuackOSM 0.1.0__py3-none-any.whl

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.
quackosm/__init__.py ADDED
@@ -0,0 +1,17 @@
1
+ """
2
+ QuackOSM.
3
+
4
+ QuackOSM is a Python library used for reading pbf (ProtoBuffer) files with OpenStreetMap data using
5
+ DuckDB spatial extension without GDAL.
6
+ """
7
+
8
+ from quackosm.functions import convert_pbf_to_gpq, get_features_gdf
9
+ from quackosm.pbf_file_reader import PbfFileReader
10
+
11
+ __version__ = "0.1.0"
12
+
13
+ __all__ = [
14
+ "PbfFileReader",
15
+ "convert_pbf_to_gpq",
16
+ "get_features_gdf",
17
+ ]
quackosm/_constants.py ADDED
@@ -0,0 +1,7 @@
1
+ """Constants used across the project."""
2
+
3
+ WGS84_CRS = "EPSG:4326"
4
+
5
+ FEATURES_INDEX = "feature_id"
6
+
7
+ GEOMETRY_COLUMN = "geometry"
@@ -0,0 +1,382 @@
1
+ # type: ignore
2
+ # pragma: no cover
3
+ """
4
+ GeoArrow IO helper module.
5
+
6
+ Will be removed after new version of https://github.com/geoarrow/geoarrow-python/ is released.
7
+ Those two functions: `read_geoparquet_table` and `write_geoparquet_table` are required in
8
+ QuackOSM library and this file is composed of files from current unreleased version of the library.
9
+ """
10
+ import json
11
+
12
+ import geoarrow.pyarrow as _ga
13
+ import pyarrow.parquet as _pq
14
+ import pyarrow.types as _types
15
+ import pyarrow_hotfix as _ # noqa: F401
16
+ from geoarrow.pyarrow._compute import ensure_storage
17
+
18
+
19
+ def read_geoparquet_table(*args, **kwargs):
20
+ """Read GeoParquet using PyArrow."""
21
+ tab = _pq.read_table(*args, **kwargs)
22
+ tab_metadata = tab.schema.metadata or {}
23
+ if b"geo" in tab_metadata:
24
+ geo_meta = json.loads(tab_metadata[b"geo"])
25
+ else:
26
+ geo_meta = {}
27
+
28
+ # Remove "geo" schema metadata key since few transformations following
29
+ # the read operation check that schema metadata is valid (e.g., column
30
+ # subset or rename)
31
+ non_geo_meta = {k: v for k, v in tab_metadata.items() if k != b"geo"}
32
+ tab = tab.replace_schema_metadata(non_geo_meta)
33
+
34
+ # Assign extension types to columns
35
+ if "columns" in geo_meta:
36
+ columns = geo_meta["columns"]
37
+ else:
38
+ columns = _geoparquet_guess_geometry_columns(tab.schema)
39
+
40
+ return _geoparquet_table_to_geoarrow(tab, columns)
41
+
42
+
43
+ def write_geoparquet_table(
44
+ table,
45
+ *args,
46
+ primary_geometry_column=None,
47
+ geometry_columns=None,
48
+ write_bbox=False,
49
+ write_geometry_types=None,
50
+ check_wkb=True,
51
+ **kwargs,
52
+ ):
53
+ """Write GeoParquet using PyArrow."""
54
+ geo_meta = _geoparquet_metadata_from_schema(
55
+ table.schema,
56
+ primary_geometry_column=primary_geometry_column,
57
+ geometry_columns=geometry_columns,
58
+ add_geometry_types=write_geometry_types,
59
+ )
60
+
61
+ # Note: this will also update geo_meta with geometry_types and bbox if requested
62
+ for i, name in enumerate(table.schema.names):
63
+ if name in geo_meta["columns"]:
64
+ table = table.set_column(
65
+ i,
66
+ name,
67
+ _geoparquet_encode_chunked_array(
68
+ table[i],
69
+ geo_meta["columns"][name],
70
+ add_geometry_types=write_geometry_types,
71
+ add_bbox=write_bbox,
72
+ check_wkb=check_wkb,
73
+ ),
74
+ )
75
+
76
+ metadata = table.schema.metadata or {}
77
+ metadata["geo"] = json.dumps(geo_meta)
78
+ table = table.replace_schema_metadata(metadata)
79
+ return _pq.write_table(table, *args, **kwargs)
80
+
81
+
82
+ def _geoparquet_guess_geometry_columns(schema):
83
+ # Only attempt guessing the "geometry" or "geography" column
84
+ columns = {}
85
+
86
+ for name in ("geometry", "geography"):
87
+ if name not in schema.names:
88
+ continue
89
+
90
+ spec = {}
91
+ type = schema.field(name).type
92
+
93
+ if _types.is_binary(type) or _types.is_large_binary(type):
94
+ spec["encoding"] = "WKB"
95
+ elif _types.is_string(type) or _types.is_large_string(type):
96
+ # WKT is not actually a geoparquet encoding but the guidance on
97
+ # putting geospatial things in parquet without metadata says you
98
+ # can do it and this is the internal sentinel for that case.
99
+ spec["encoding"] = "WKT"
100
+
101
+ # A column named "geography" has spherical edges according to the
102
+ # compatible Parquet guidance.
103
+ if name == "geography":
104
+ spec["edges"] = "spherical"
105
+
106
+ columns[name] = spec
107
+
108
+ return columns
109
+
110
+
111
+ def _geoparquet_chunked_array_to_geoarrow(item, spec):
112
+ # If item was written as a GeoArrow extension type to the Parquet file,
113
+ # ignore any information in the column spec
114
+ if isinstance(item.type, _ga.GeometryExtensionType):
115
+ return item
116
+
117
+ if "encoding" not in spec:
118
+ raise ValueError("Invalid GeoParquet column specification: missing 'encoding'")
119
+
120
+ encoding = spec["encoding"]
121
+ if encoding in ("WKB", "WKT"):
122
+ item = _ga.array(item)
123
+ else:
124
+ raise ValueError(f"Invalid GeoParquet encoding value: '{encoding}'")
125
+
126
+ if "crs" not in spec:
127
+ crs = json.dumps(_CRS_LONLAT).encode("UTF-8")
128
+ else:
129
+ crs = json.dumps(spec["crs"]).encode("UTF-8")
130
+
131
+ if "edges" not in spec or spec["edges"] == "planar":
132
+ edge_type = None
133
+ elif spec["edges"] == "spherical":
134
+ edge_type = _ga.EdgeType.SPHERICAL
135
+ else:
136
+ raise ValueError("Invalid GeoParquet column edges value")
137
+
138
+ if crs is not None:
139
+ item = _ga.with_crs(item, crs)
140
+
141
+ if edge_type is not None:
142
+ item = _ga.with_edge_type(item, edge_type)
143
+
144
+ return item
145
+
146
+
147
+ def _geoparquet_table_to_geoarrow(tab, columns):
148
+ tab_names = set(tab.schema.names)
149
+ for col_name, spec in columns.items():
150
+ # col_name might not exist if only a subset of columns were read from file
151
+ if col_name not in tab_names:
152
+ continue
153
+
154
+ col_i = tab.schema.get_field_index(col_name)
155
+ new_geometry = _geoparquet_chunked_array_to_geoarrow(tab[col_i], spec)
156
+ tab = tab.set_column(col_i, col_name, new_geometry)
157
+
158
+ return tab
159
+
160
+
161
+ def _geoparquet_guess_primary_geometry_column(schema, primary_geometry_column=None):
162
+ if primary_geometry_column is not None:
163
+ return primary_geometry_column
164
+
165
+ # If there's a "geometry" or "geography" column, pick that one
166
+ if "geometry" in schema.names:
167
+ return "geometry"
168
+ elif "geography" in schema.names:
169
+ return "geography"
170
+
171
+ # Otherwise, pick the first thing we know is actually geometry
172
+ for name, type in zip(schema.names, schema.types):
173
+ if isinstance(type, _ga.GeometryExtensionType):
174
+ return name
175
+
176
+ raise ValueError("write_geoparquet_table() requires source with at least one geometry column")
177
+
178
+
179
+ def _geoparquet_column_spec_from_type(type, add_geometry_types=None):
180
+ # We always encode to WKB since it's the only supported value
181
+ spec = {"encoding": "WKB", "geometry_types": []}
182
+
183
+ # Pass along extra information from GeoArrow extension type metadata
184
+ if isinstance(type, _ga.GeometryExtensionType):
185
+ if type.crs_type == _ga.CrsType.PROJJSON:
186
+ spec["crs"] = json.loads(type.crs)
187
+ elif type.crs_type == _ga.CrsType.NONE:
188
+ spec["crs"] = None
189
+ else:
190
+ import pyproj
191
+
192
+ spec["crs"] = pyproj.CRS(type.crs).to_json_dict()
193
+
194
+ if type.edge_type == _ga.EdgeType.SPHERICAL:
195
+ spec["edges"] = "spherical"
196
+
197
+ # GeoArrow-encoded types can confidently declare a single geometry type
198
+ maybe_known_geometry_type = type.geometry_type
199
+ maybe_known_dimensions = type.dimensions
200
+ if (
201
+ add_geometry_types is not False
202
+ and maybe_known_geometry_type != _ga.GeometryType.GEOMETRY
203
+ and maybe_known_dimensions != _ga.Dimensions.UNKNOWN
204
+ ):
205
+ geometry_type = _GEOPARQUET_GEOMETRY_TYPE_LABELS[maybe_known_geometry_type]
206
+ dimensions = _GEOPARQUET_DIMENSION_LABELS[maybe_known_dimensions]
207
+ spec["geometry_types"] = [f"{geometry_type}{dimensions}"]
208
+
209
+ return spec
210
+
211
+
212
+ def _geoparquet_columns_from_schema(
213
+ schema, geometry_columns=None, primary_geometry_column=None, add_geometry_types=None
214
+ ):
215
+ schema_names = schema.names
216
+ schema_types = schema.types
217
+
218
+ if geometry_columns is None:
219
+ geometry_columns = set()
220
+ if primary_geometry_column is not None:
221
+ geometry_columns.add(primary_geometry_column)
222
+
223
+ for name, type in zip(schema_names, schema_types):
224
+ if isinstance(type, _ga.GeometryExtensionType):
225
+ geometry_columns.add(name)
226
+ else:
227
+ geometry_columns = set(geometry_columns)
228
+
229
+ specs = {}
230
+ for name, type in zip(schema_names, schema_types):
231
+ if name in geometry_columns:
232
+ specs[name] = _geoparquet_column_spec_from_type(
233
+ type, add_geometry_types=add_geometry_types
234
+ )
235
+
236
+ return specs
237
+
238
+
239
+ def _geoparquet_metadata_from_schema(
240
+ schema, geometry_columns=None, primary_geometry_column=None, add_geometry_types=None
241
+ ):
242
+ primary_geometry_column = _geoparquet_guess_primary_geometry_column(
243
+ schema, primary_geometry_column
244
+ )
245
+ columns = _geoparquet_columns_from_schema(
246
+ schema, geometry_columns, add_geometry_types=add_geometry_types
247
+ )
248
+ return {
249
+ "version": "1.0.0",
250
+ "primary_column": primary_geometry_column,
251
+ "columns": columns,
252
+ }
253
+
254
+
255
+ def _geoparquet_update_spec_geometry_types(item, spec):
256
+ geometry_type_labels = []
257
+ for element in _ga.unique_geometry_types(item).to_pylist():
258
+ geometry_type = _GEOPARQUET_GEOMETRY_TYPE_LABELS[element["geometry_type"]]
259
+ dimensions = _GEOPARQUET_DIMENSION_LABELS[element["dimensions"]]
260
+ geometry_type_labels.append(f"{geometry_type}{dimensions}")
261
+
262
+ spec["geometry_types"] = geometry_type_labels
263
+
264
+
265
+ def _geoparquet_update_spec_bbox(item, spec):
266
+ box = _ga.box_agg(item).as_py()
267
+ spec["bbox"] = [box["xmin"], box["ymin"], box["xmax"], box["ymax"]]
268
+
269
+
270
+ def _geoparquet_encode_chunked_array(
271
+ item, spec, add_geometry_types=None, add_bbox=False, check_wkb=True
272
+ ):
273
+ # ...because we're currently only ever encoding using WKB
274
+ if spec["encoding"] == "WKB":
275
+ item_out = _ga.as_wkb(item)
276
+ else:
277
+ encoding = spec["encoding"]
278
+ raise ValueError(f"Expected column encoding 'WKB' but got '{encoding}'")
279
+
280
+ # For everything except a well-known text-encoded column, we want to do
281
+ # calculations on the pre-WKB-encoded value.
282
+ if spec["encoding"] == "WKT":
283
+ item_calc = item_out
284
+ else:
285
+ item_calc = item
286
+
287
+ # geometry_types that are fixed at the data type level have already been
288
+ # added to the spec in an earlier step. The unique_geometry_types()
289
+ # function is sufficiently optimized such that this potential
290
+ # re-computation is not expensive.
291
+ if add_geometry_types is True:
292
+ _geoparquet_update_spec_geometry_types(item_calc, spec)
293
+
294
+ if add_bbox:
295
+ _geoparquet_update_spec_bbox(item_calc, spec)
296
+
297
+ return ensure_storage(item_out)
298
+
299
+
300
+ _GEOPARQUET_GEOMETRY_TYPE_LABELS = [
301
+ "Geometry",
302
+ "Point",
303
+ "LineString",
304
+ "Polygon",
305
+ "MultiPoint",
306
+ "MultiLineString",
307
+ "MultiPolygon",
308
+ ]
309
+
310
+ _GEOPARQUET_DIMENSION_LABELS = [None, "", " Z", " M", " ZM"]
311
+
312
+ _CRS_LONLAT = {
313
+ "$schema": "https://proj.org/schemas/v0.7/projjson.schema.json",
314
+ "type": "GeographicCRS",
315
+ "name": "WGS 84 (CRS84)",
316
+ "datum_ensemble": {
317
+ "name": "World Geodetic System 1984 ensemble",
318
+ "members": [
319
+ {
320
+ "name": "World Geodetic System 1984 (Transit)",
321
+ "id": {"authority": "EPSG", "code": 1166},
322
+ },
323
+ {
324
+ "name": "World Geodetic System 1984 (G730)",
325
+ "id": {"authority": "EPSG", "code": 1152},
326
+ },
327
+ {
328
+ "name": "World Geodetic System 1984 (G873)",
329
+ "id": {"authority": "EPSG", "code": 1153},
330
+ },
331
+ {
332
+ "name": "World Geodetic System 1984 (G1150)",
333
+ "id": {"authority": "EPSG", "code": 1154},
334
+ },
335
+ {
336
+ "name": "World Geodetic System 1984 (G1674)",
337
+ "id": {"authority": "EPSG", "code": 1155},
338
+ },
339
+ {
340
+ "name": "World Geodetic System 1984 (G1762)",
341
+ "id": {"authority": "EPSG", "code": 1156},
342
+ },
343
+ {
344
+ "name": "World Geodetic System 1984 (G2139)",
345
+ "id": {"authority": "EPSG", "code": 1309},
346
+ },
347
+ ],
348
+ "ellipsoid": {
349
+ "name": "WGS 84",
350
+ "semi_major_axis": 6378137,
351
+ "inverse_flattening": 298.257223563,
352
+ },
353
+ "accuracy": "2.0",
354
+ "id": {"authority": "EPSG", "code": 6326},
355
+ },
356
+ "coordinate_system": {
357
+ "subtype": "ellipsoidal",
358
+ "axis": [
359
+ {
360
+ "name": "Geodetic longitude",
361
+ "abbreviation": "Lon",
362
+ "direction": "east",
363
+ "unit": "degree",
364
+ },
365
+ {
366
+ "name": "Geodetic latitude",
367
+ "abbreviation": "Lat",
368
+ "direction": "north",
369
+ "unit": "degree",
370
+ },
371
+ ],
372
+ },
373
+ "scope": "Not known.",
374
+ "area": "World.",
375
+ "bbox": {
376
+ "south_latitude": -90,
377
+ "west_longitude": -180,
378
+ "north_latitude": 90,
379
+ "east_longitude": 180,
380
+ },
381
+ "id": {"authority": "OGC", "code": "CRS84"},
382
+ }
@@ -0,0 +1,130 @@
1
+ """Module contains a dedicated type alias for OSM tags filter."""
2
+
3
+ from collections.abc import Iterable
4
+ from typing import Union, cast, overload
5
+
6
+ from quackosm._typing import is_expected_type
7
+
8
+ OsmTagsFilter = dict[str, Union[list[str], str, bool]]
9
+
10
+ GroupedOsmTagsFilter = dict[str, OsmTagsFilter]
11
+
12
+
13
+ @overload
14
+ def merge_osm_tags_filter(osm_tags_filter: OsmTagsFilter) -> OsmTagsFilter: ...
15
+
16
+
17
+ @overload
18
+ def merge_osm_tags_filter(osm_tags_filter: GroupedOsmTagsFilter) -> OsmTagsFilter: ...
19
+
20
+
21
+ @overload
22
+ def merge_osm_tags_filter(osm_tags_filter: Iterable[OsmTagsFilter]) -> OsmTagsFilter: ...
23
+
24
+
25
+ @overload
26
+ def merge_osm_tags_filter(osm_tags_filter: Iterable[GroupedOsmTagsFilter]) -> OsmTagsFilter: ...
27
+
28
+
29
+ def merge_osm_tags_filter(
30
+ osm_tags_filter: Union[
31
+ OsmTagsFilter, GroupedOsmTagsFilter, Iterable[OsmTagsFilter], Iterable[GroupedOsmTagsFilter]
32
+ ]
33
+ ) -> OsmTagsFilter:
34
+ """
35
+ Merge OSM tags filter into `OsmTagsFilter` type.
36
+
37
+ Optionally merges `GroupedOsmTagsFilter` into `OsmTagsFilter` to allow loaders to load all
38
+ defined groups during single operation.
39
+
40
+ Args:
41
+ osm_tags_filter: OSM tags filter definition.
42
+
43
+ Raises:
44
+ AttributeError: When provided tags don't match both
45
+ `OsmTagsFilter` or `GroupedOsmTagsFilter`.
46
+
47
+ Returns:
48
+ OsmTagsFilter: Merged filters.
49
+ """
50
+ if is_expected_type(osm_tags_filter, OsmTagsFilter):
51
+ return cast(OsmTagsFilter, osm_tags_filter)
52
+ elif is_expected_type(osm_tags_filter, GroupedOsmTagsFilter):
53
+ return _merge_grouped_osm_tags_filter(cast(GroupedOsmTagsFilter, osm_tags_filter))
54
+ elif is_expected_type(osm_tags_filter, Iterable):
55
+ return _merge_multiple_osm_tags_filters(
56
+ [
57
+ merge_osm_tags_filter(
58
+ cast(Union[OsmTagsFilter, GroupedOsmTagsFilter], sub_osm_tags_filter)
59
+ )
60
+ for sub_osm_tags_filter in osm_tags_filter
61
+ ]
62
+ )
63
+
64
+ raise AttributeError(
65
+ "Provided tags don't match required type definitions"
66
+ " (OsmTagsFilter or GroupedOsmTagsFilter)."
67
+ )
68
+
69
+
70
+ def _merge_grouped_osm_tags_filter(grouped_filter: GroupedOsmTagsFilter) -> OsmTagsFilter:
71
+ """
72
+ Merge grouped osm tags filter into a base one.
73
+
74
+ Function merges all filter categories into a single one for an OSM loader to use.
75
+
76
+ Args:
77
+ grouped_filter (GroupedOsmTagsFilter): Grouped filter to be merged into a single one.
78
+
79
+ Returns:
80
+ osm_tags_type: Merged filter.
81
+ """
82
+ if not is_expected_type(grouped_filter, GroupedOsmTagsFilter):
83
+ raise ValueError(
84
+ "Provided filter doesn't match required `GroupedOsmTagsFilter` definition."
85
+ )
86
+
87
+ return _merge_multiple_osm_tags_filters(grouped_filter.values())
88
+
89
+
90
+ def _merge_multiple_osm_tags_filters(osm_tags_filters: Iterable[OsmTagsFilter]) -> OsmTagsFilter:
91
+ """
92
+ Merge multiple osm tags filters into a single one.
93
+
94
+ Function merges all OsmTagsFilters into a single one for an OSM loader to use.
95
+
96
+ Args:
97
+ osm_tags_filters (Iterable[OsmTagsFilter]): List of filters to be merged into a single one.
98
+
99
+ Returns:
100
+ osm_tags_type: Merged filter.
101
+ """
102
+ if not is_expected_type(osm_tags_filters, Iterable[OsmTagsFilter]):
103
+ raise ValueError(
104
+ "Provided filter doesn't match required `Iterable[OsmTagsFilter]` definition."
105
+ )
106
+
107
+ result: OsmTagsFilter = {}
108
+ for osm_tags_filter in osm_tags_filters:
109
+ for osm_tag_key, osm_tag_value in osm_tags_filter.items():
110
+ if osm_tag_key not in result:
111
+ result[osm_tag_key] = []
112
+
113
+ # If filter is already a positive boolean, skip
114
+ if isinstance(result[osm_tag_key], bool) and result[osm_tag_key]:
115
+ continue
116
+
117
+ current_values_list = cast(list[str], result[osm_tag_key])
118
+
119
+ # Check bool
120
+ if isinstance(osm_tag_value, bool) and osm_tag_value:
121
+ result[osm_tag_key] = True
122
+ # Check string
123
+ elif isinstance(osm_tag_value, str) and osm_tag_value not in current_values_list:
124
+ current_values_list.append(osm_tag_value)
125
+ # Check list
126
+ elif isinstance(osm_tag_value, list):
127
+ new_values = [value for value in osm_tag_value if value not in current_values_list]
128
+ current_values_list.extend(new_values)
129
+
130
+ return result
@@ -0,0 +1,127 @@
1
+ from collections.abc import Iterable
2
+ from typing import Any, NamedTuple, cast
3
+
4
+ from quackosm._typing import is_expected_type
5
+
6
+
7
+ class OsmWayPolygonConfig(NamedTuple):
8
+ """OSM Way polygon features config object."""
9
+
10
+ all: Iterable[str]
11
+ allowlist: dict[str, Iterable[str]]
12
+ denylist: dict[str, Iterable[str]]
13
+
14
+
15
+ def parse_dict_to_config_object(raw_config: dict[str, Any]) -> OsmWayPolygonConfig:
16
+ all_tags = raw_config.get("all", [])
17
+ allowlist_tags = raw_config.get("allowlist", {})
18
+ denylist_tags = raw_config.get("denylist", {})
19
+ if not is_expected_type(all_tags, Iterable[str]):
20
+ raise ValueError(f"Wrong type of key: all ({type(all_tags)})")
21
+
22
+ if not is_expected_type(allowlist_tags, dict[str, Iterable[str]]):
23
+ raise ValueError(f"Wrong type of key: all ({type(allowlist_tags)})")
24
+
25
+ if not is_expected_type(denylist_tags, dict[str, Iterable[str]]):
26
+ raise ValueError(f"Wrong type of key: denylist ({type(denylist_tags)})")
27
+
28
+ return OsmWayPolygonConfig(
29
+ all=cast(Iterable[str], all_tags),
30
+ allowlist=cast(dict[str, Iterable[str]], allowlist_tags),
31
+ denylist=cast(dict[str, Iterable[str]], denylist_tags),
32
+ )
33
+
34
+
35
+ # Config based on two sources + manual OSM wiki check
36
+ # 1. https://github.com/tyrasd/osm-polygon-features/blob/master/polygon-features.json
37
+ # 2. https://github.com/ideditor/id-area-keys/blob/main/areaKeys.json
38
+ OSM_WAY_POLYGON_CONFIG_RAW = {
39
+ "all": [
40
+ "allotments",
41
+ "area:highway",
42
+ "boundary",
43
+ "bridge:support",
44
+ "building:part",
45
+ "building",
46
+ "cemetery",
47
+ "club",
48
+ "craft",
49
+ "demolished:building",
50
+ "disused:amenity",
51
+ "disused:leisure",
52
+ "disused:shop",
53
+ "healthcare",
54
+ "historic",
55
+ "industrial",
56
+ "internet_access",
57
+ "junction",
58
+ "landuse",
59
+ "leisure",
60
+ "office",
61
+ "place",
62
+ "police",
63
+ "polling_station",
64
+ "public_transport",
65
+ "residential",
66
+ "ruins",
67
+ "seamark:type",
68
+ "shop",
69
+ "sport",
70
+ "telecom",
71
+ "tourism",
72
+ ],
73
+ "allowlist": {
74
+ "advertising": ["sculpture", "sign"],
75
+ "aerialway": ["station"],
76
+ "barrier": ["city_wall", "hedge", "wall", "toll_booth"],
77
+ "highway": ["services", "rest_area", "platform"],
78
+ "railway": ["station", "turntable", "roundhouse", "platform"],
79
+ "waterway": ["riverbank", "dock", "boatyard", "dam", "fuel"],
80
+ },
81
+ "denylist": {
82
+ "aeroway": ["jet_bridge", "parking_position", "taxiway", "no"],
83
+ "amenity": ["bench", "weighbridge"],
84
+ "attraction": ["river_rafting", "train", "water_slide", "boat_ride"],
85
+ "emergency": ["designated", "destination", "no", "official", "private", "yes"],
86
+ "geological": ["volcanic_caldera_rim", "fault"],
87
+ "golf": ["cartpath", "hole", "path"],
88
+ "indoor": ["corridor", "wall"],
89
+ "man_made": [
90
+ "yes",
91
+ "breakwater",
92
+ "carpet_hanger",
93
+ "crane",
94
+ "cutline",
95
+ "dyke",
96
+ "embankment",
97
+ "goods_conveyor",
98
+ "groyne",
99
+ "pier",
100
+ "pipeline",
101
+ "torii",
102
+ "video_wall",
103
+ ],
104
+ "military": ["trench"],
105
+ "natural": [
106
+ "bay",
107
+ "cliff",
108
+ "coastline",
109
+ "ridge",
110
+ "strait",
111
+ "tree_row",
112
+ "valley",
113
+ "no",
114
+ "arete",
115
+ ],
116
+ "piste:type": ["downhill", "hike", "ice_skate", "nordic", "skitour", "sled", "sleigh"],
117
+ "playground": [
118
+ "balancebeam",
119
+ "rope_traverse",
120
+ "stepping_stone",
121
+ "stepping_post",
122
+ "rope_swing",
123
+ "climbing_slope",
124
+ ],
125
+ "power": ["cable", "line", "minor_line", "insulator", "busbar", "bay", "portal"],
126
+ },
127
+ }
quackosm/_typing.py ADDED
@@ -0,0 +1,28 @@
1
+ """Utility function for typing purposes."""
2
+
3
+ from contextlib import suppress
4
+ from typing import Any
5
+
6
+ from typeguard import TypeCheckError, check_type
7
+
8
+
9
+ def is_expected_type(value: object, expected_type: Any) -> bool:
10
+ """
11
+ Check if an object is a given type.
12
+
13
+ Uses `typeguard` library to check objects using `typing` definitions.
14
+
15
+ Args:
16
+ value (object): Value to be checked against `expected_type`.
17
+ expected_type (Any): A class or generic type instance.
18
+
19
+ Returns:
20
+ bool: Flag whether the object is an instance of the required type.
21
+ """
22
+ result = False
23
+
24
+ with suppress(TypeCheckError):
25
+ check_type(value, expected_type)
26
+ result = True
27
+
28
+ return result