getopendatafvg 0.2.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.
- getopendatafvg/__init__.py +119 -0
- getopendatafvg/_errors.py +6 -0
- getopendatafvg/avalanche.py +135 -0
- getopendatafvg/catalog.py +838 -0
- getopendatafvg/catasto.py +82 -0
- getopendatafvg/coverage.py +75 -0
- getopendatafvg/dem.py +132 -0
- getopendatafvg/elevation.py +37 -0
- getopendatafvg/indices.py +142 -0
- getopendatafvg/istat.py +421 -0
- getopendatafvg/landsat.py +211 -0
- getopendatafvg/open_data_fvg.py +137 -0
- getopendatafvg/overpass.py +94 -0
- getopendatafvg/pmtiles.py +51 -0
- getopendatafvg/scene_date.py +35 -0
- getopendatafvg/sentinel2.py +224 -0
- getopendatafvg/vectorize.py +120 -0
- getopendatafvg/viz.py +35 -0
- getopendatafvg/weather.py +54 -0
- getopendatafvg/wfs.py +95 -0
- getopendatafvg/wikipedia.py +65 -0
- getopendatafvg-0.2.0.dist-info/METADATA +525 -0
- getopendatafvg-0.2.0.dist-info/RECORD +25 -0
- getopendatafvg-0.2.0.dist-info/WHEEL +4 -0
- getopendatafvg-0.2.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""getopendatafvg: extract and visualize open geographic, socio-economic and
|
|
2
|
+
environmental data for Friuli Venezia Giulia.
|
|
3
|
+
|
|
4
|
+
This is an early, incremental extraction from the data pipeline behind
|
|
5
|
+
Mappa Civica (https://github.com/BeneComune/mappa-civica), a civic mapping
|
|
6
|
+
platform for the comune of Montereale Valcellina. Modules are being ported
|
|
7
|
+
and generalized one at a time rather than all at once, so the surface area
|
|
8
|
+
here will keep growing.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from ._errors import NoCleanSceneFoundError
|
|
12
|
+
from .avalanche import AvalancheSite, fetch_avalanche_sites
|
|
13
|
+
from .catalog import (
|
|
14
|
+
CATALOG,
|
|
15
|
+
KnownDataset,
|
|
16
|
+
fetch_known_dataset,
|
|
17
|
+
list_known_datasets,
|
|
18
|
+
search_known_datasets,
|
|
19
|
+
)
|
|
20
|
+
from .catasto import fetch_cadastral_parcels, region_parquet_name
|
|
21
|
+
from .coverage import build_coverage_union, line_coverage_pct
|
|
22
|
+
from .dem import DemSampler, compute_line_grade, compute_line_slope, slope_class_for_value
|
|
23
|
+
from .elevation import fetch_elevation
|
|
24
|
+
from .indices import IndexRaster, compute_lst, compute_nbr, compute_ndvi
|
|
25
|
+
from .istat import (
|
|
26
|
+
FVG_NUTS_AREAS,
|
|
27
|
+
fetch_bank_branches,
|
|
28
|
+
fetch_consumer_price_index,
|
|
29
|
+
fetch_demographic_balance,
|
|
30
|
+
fetch_demographic_indicators,
|
|
31
|
+
fetch_employment_rate,
|
|
32
|
+
fetch_income_series,
|
|
33
|
+
fetch_istat_dataflow,
|
|
34
|
+
fetch_population_series,
|
|
35
|
+
fetch_tourism_capacity,
|
|
36
|
+
fetch_tourism_flows,
|
|
37
|
+
)
|
|
38
|
+
from .landsat import LandsatScene, UsgsCredentials, fetch_landsat_scene
|
|
39
|
+
from .open_data_fvg import (
|
|
40
|
+
PortalAsset,
|
|
41
|
+
fetch_open_data_fvg,
|
|
42
|
+
lon_lat_box_clause,
|
|
43
|
+
search_open_data_fvg,
|
|
44
|
+
within_box_clause,
|
|
45
|
+
)
|
|
46
|
+
from .overpass import boundary_poly_filter, element_point, fetch_overpass_elements, query_overpass
|
|
47
|
+
from .pmtiles import TippecanoeNotFoundError, build_pmtiles
|
|
48
|
+
from .scene_date import landsat_scene_date, sentinel2_scene_date
|
|
49
|
+
from .sentinel2 import DEFAULT_BANDS, Band, CdseCredentials, Sentinel2Scene, fetch_sentinel2_scene
|
|
50
|
+
from .vectorize import ClassBreak, classify_and_vectorize
|
|
51
|
+
from .viz import plot_index
|
|
52
|
+
from .weather import DailyWeather, fetch_historical_weather
|
|
53
|
+
from .wfs import clip_to_boundary, fetch_and_clip_wfs_features, fetch_wfs_features
|
|
54
|
+
from .wikipedia import fetch_wikipedia_summary
|
|
55
|
+
|
|
56
|
+
__all__ = [
|
|
57
|
+
'CATALOG',
|
|
58
|
+
'DEFAULT_BANDS',
|
|
59
|
+
'FVG_NUTS_AREAS',
|
|
60
|
+
'AvalancheSite',
|
|
61
|
+
'Band',
|
|
62
|
+
'CdseCredentials',
|
|
63
|
+
'ClassBreak',
|
|
64
|
+
'DailyWeather',
|
|
65
|
+
'DemSampler',
|
|
66
|
+
'IndexRaster',
|
|
67
|
+
'KnownDataset',
|
|
68
|
+
'LandsatScene',
|
|
69
|
+
'NoCleanSceneFoundError',
|
|
70
|
+
'PortalAsset',
|
|
71
|
+
'Sentinel2Scene',
|
|
72
|
+
'TippecanoeNotFoundError',
|
|
73
|
+
'UsgsCredentials',
|
|
74
|
+
'boundary_poly_filter',
|
|
75
|
+
'build_coverage_union',
|
|
76
|
+
'build_pmtiles',
|
|
77
|
+
'classify_and_vectorize',
|
|
78
|
+
'clip_to_boundary',
|
|
79
|
+
'compute_line_grade',
|
|
80
|
+
'compute_line_slope',
|
|
81
|
+
'compute_lst',
|
|
82
|
+
'compute_nbr',
|
|
83
|
+
'compute_ndvi',
|
|
84
|
+
'element_point',
|
|
85
|
+
'fetch_and_clip_wfs_features',
|
|
86
|
+
'fetch_avalanche_sites',
|
|
87
|
+
'fetch_bank_branches',
|
|
88
|
+
'fetch_cadastral_parcels',
|
|
89
|
+
'fetch_consumer_price_index',
|
|
90
|
+
'fetch_demographic_balance',
|
|
91
|
+
'fetch_demographic_indicators',
|
|
92
|
+
'fetch_elevation',
|
|
93
|
+
'fetch_employment_rate',
|
|
94
|
+
'fetch_historical_weather',
|
|
95
|
+
'fetch_income_series',
|
|
96
|
+
'fetch_istat_dataflow',
|
|
97
|
+
'fetch_known_dataset',
|
|
98
|
+
'fetch_landsat_scene',
|
|
99
|
+
'fetch_open_data_fvg',
|
|
100
|
+
'fetch_overpass_elements',
|
|
101
|
+
'fetch_population_series',
|
|
102
|
+
'fetch_sentinel2_scene',
|
|
103
|
+
'fetch_tourism_capacity',
|
|
104
|
+
'fetch_tourism_flows',
|
|
105
|
+
'fetch_wfs_features',
|
|
106
|
+
'fetch_wikipedia_summary',
|
|
107
|
+
'landsat_scene_date',
|
|
108
|
+
'line_coverage_pct',
|
|
109
|
+
'list_known_datasets',
|
|
110
|
+
'lon_lat_box_clause',
|
|
111
|
+
'plot_index',
|
|
112
|
+
'query_overpass',
|
|
113
|
+
'region_parquet_name',
|
|
114
|
+
'search_known_datasets',
|
|
115
|
+
'search_open_data_fvg',
|
|
116
|
+
'sentinel2_scene_date',
|
|
117
|
+
'slope_class_for_value',
|
|
118
|
+
'within_box_clause',
|
|
119
|
+
]
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
class NoCleanSceneFoundError(RuntimeError):
|
|
2
|
+
"""No scene under the cloud-cover threshold was found within the
|
|
3
|
+
lookback window. Shared by every scene-fetching module (Sentinel-2,
|
|
4
|
+
Landsat, ...) since it means the same thing regardless of provider:
|
|
5
|
+
leave whatever data you already have alone, don't wipe it.
|
|
6
|
+
"""
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""Avalanche sites in Friuli Venezia Giulia, merged from the region's two
|
|
2
|
+
avalanche layers into one list.
|
|
3
|
+
|
|
4
|
+
The region publishes the Catasto Valanghe as two WFS layers, and the
|
|
5
|
+
split is by survey method rather than by area: `CV_VALANGHE_RILEVATE`
|
|
6
|
+
holds sites surveyed on the ground (3875 features) and
|
|
7
|
+
`CV_VALANGHE_FOTOINT` sites mapped from aerial photography (3255). They
|
|
8
|
+
do not overlap - checked live, the two share not one `ID_SITO` - so a
|
|
9
|
+
caller wanting "avalanche sites here" needs both and has no way to tell
|
|
10
|
+
from either layer alone that the other exists. That is what this module
|
|
11
|
+
is for.
|
|
12
|
+
|
|
13
|
+
Deliberately not a hazard model. The region publishes perimeters and
|
|
14
|
+
site attributes, not a hazard class, so this exposes what the data says
|
|
15
|
+
(release and runout elevations, aspect, site type, which survey it came
|
|
16
|
+
from) and stops there. Anything resembling a danger rating would be
|
|
17
|
+
invented here rather than sourced, and the catalog entries are still the
|
|
18
|
+
way to get at the raw layers.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from dataclasses import dataclass
|
|
24
|
+
from typing import Any
|
|
25
|
+
|
|
26
|
+
from shapely.geometry import shape
|
|
27
|
+
from shapely.geometry.base import BaseGeometry
|
|
28
|
+
|
|
29
|
+
from .catalog import WFS_BASE_URL
|
|
30
|
+
from .wfs import fetch_and_clip_wfs_features, fetch_wfs_features
|
|
31
|
+
|
|
32
|
+
SURVEYED_LAYER = 'ZONE_RISC:CV_VALANGHE_RILEVATE'
|
|
33
|
+
PHOTO_INTERPRETED_LAYER = 'ZONE_RISC:CV_VALANGHE_FOTOINT'
|
|
34
|
+
|
|
35
|
+
# Both layers are several thousand features and fetch_wfs_features caps
|
|
36
|
+
# at 1000 by default, which would silently truncate to a quarter of the
|
|
37
|
+
# catasto. This is above the current totals with room to grow.
|
|
38
|
+
DEFAULT_COUNT = 10000
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(frozen=True)
|
|
42
|
+
class AvalancheSite:
|
|
43
|
+
"""One avalanche site. `geometries` is a tuple because a site is
|
|
44
|
+
occasionally stored as more than one polygon row sharing an
|
|
45
|
+
`ID_SITO` - two such sites exist in the surveyed layer - and dropping
|
|
46
|
+
the extra rows would quietly shrink the site's footprint.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
site_id: int | None
|
|
50
|
+
comune: str
|
|
51
|
+
toponym: str
|
|
52
|
+
site_type: str
|
|
53
|
+
release_elevation_max_m: int | None
|
|
54
|
+
runout_elevation_min_m: int | None
|
|
55
|
+
aspect: str
|
|
56
|
+
photo_interpreted: bool
|
|
57
|
+
geometries: tuple[BaseGeometry, ...]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def fetch_avalanche_sites(
|
|
61
|
+
boundary: BaseGeometry | None = None,
|
|
62
|
+
photo_interpreted: bool | None = None,
|
|
63
|
+
count: int = DEFAULT_COUNT,
|
|
64
|
+
) -> list[AvalancheSite]:
|
|
65
|
+
"""Avalanche sites from both layers as one list, sorted by comune then
|
|
66
|
+
toponym.
|
|
67
|
+
|
|
68
|
+
With a `boundary` the geometries come back clipped to it, exactly,
|
|
69
|
+
and sites falling outside are dropped. Without one, every site in the
|
|
70
|
+
region is returned - about 7100 of them.
|
|
71
|
+
|
|
72
|
+
`photo_interpreted` filters by survey method: `False` for
|
|
73
|
+
ground-surveyed sites only, `True` for photo-interpreted only, `None`
|
|
74
|
+
(the default) for both, which is the point of the module.
|
|
75
|
+
"""
|
|
76
|
+
sites: list[AvalancheSite] = []
|
|
77
|
+
layers = {SURVEYED_LAYER: False, PHOTO_INTERPRETED_LAYER: True}
|
|
78
|
+
for type_name, from_photo in layers.items():
|
|
79
|
+
if photo_interpreted is not None and photo_interpreted != from_photo:
|
|
80
|
+
continue
|
|
81
|
+
sites.extend(_sites_from_layer(type_name, from_photo, boundary, count))
|
|
82
|
+
return sorted(sites, key=lambda s: (s.comune, s.toponym))
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _sites_from_layer(
|
|
86
|
+
type_name: str,
|
|
87
|
+
from_photo: bool,
|
|
88
|
+
boundary: BaseGeometry | None,
|
|
89
|
+
count: int,
|
|
90
|
+
) -> list[AvalancheSite]:
|
|
91
|
+
if boundary is not None:
|
|
92
|
+
pairs = fetch_and_clip_wfs_features(
|
|
93
|
+
WFS_BASE_URL, type_name, boundary, count=count
|
|
94
|
+
)
|
|
95
|
+
else:
|
|
96
|
+
pairs = [
|
|
97
|
+
(feature.get('properties', {}), shape(feature['geometry']))
|
|
98
|
+
for feature in fetch_wfs_features(WFS_BASE_URL, type_name, count=count)
|
|
99
|
+
if feature.get('geometry')
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
# Group the multi-row sites back together before building anything.
|
|
103
|
+
grouped: dict[Any, list[tuple[dict[str, Any], BaseGeometry]]] = {}
|
|
104
|
+
for props, geometry in pairs:
|
|
105
|
+
grouped.setdefault(props.get('ID_SITO'), []).append((props, geometry))
|
|
106
|
+
|
|
107
|
+
sites = []
|
|
108
|
+
for site_id, rows in grouped.items():
|
|
109
|
+
props = rows[0][0]
|
|
110
|
+
sites.append(
|
|
111
|
+
AvalancheSite(
|
|
112
|
+
site_id=site_id,
|
|
113
|
+
comune=str(props.get('COMUNE') or ''),
|
|
114
|
+
toponym=str(props.get('TOPONIMO') or ''),
|
|
115
|
+
site_type=str(props.get('TIPO_SITO') or ''),
|
|
116
|
+
release_elevation_max_m=_as_int(props.get('QUOTA_DISTACCO_MAX')),
|
|
117
|
+
runout_elevation_min_m=_as_int(props.get('QUOTA_ARRESTO_MIN')),
|
|
118
|
+
aspect=str(props.get('ESPOSIZIONE_SITO') or ''),
|
|
119
|
+
photo_interpreted=from_photo,
|
|
120
|
+
geometries=tuple(geometry for _, geometry in rows),
|
|
121
|
+
)
|
|
122
|
+
)
|
|
123
|
+
return sites
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _as_int(value: Any) -> int | None:
|
|
127
|
+
"""Elevations arrive as numbers, but a missing one can be an empty
|
|
128
|
+
string rather than null, which int() would raise on.
|
|
129
|
+
"""
|
|
130
|
+
if value in (None, ''):
|
|
131
|
+
return None
|
|
132
|
+
try:
|
|
133
|
+
return int(float(value))
|
|
134
|
+
except (TypeError, ValueError):
|
|
135
|
+
return None
|