monumenten 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.
- monumenten/__init__.py +5 -0
- monumenten/_api/__init__.py +0 -0
- monumenten/_api/_cultureel_erfgoed.py +161 -0
- monumenten/_api/_kadaster.py +77 -0
- monumenten/_processing.py +151 -0
- monumenten/_version.py +16 -0
- monumenten/client.py +110 -0
- monumenten/logging.py +0 -0
- monumenten-0.1.0.dist-info/LICENSE +21 -0
- monumenten-0.1.0.dist-info/METADATA +169 -0
- monumenten-0.1.0.dist-info/RECORD +13 -0
- monumenten-0.1.0.dist-info/WHEEL +5 -0
- monumenten-0.1.0.dist-info/top_level.txt +1 -0
monumenten/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Any, Dict, List, Optional
|
|
4
|
+
|
|
5
|
+
import aiohttp
|
|
6
|
+
|
|
7
|
+
# Create a module-level logger
|
|
8
|
+
logger = logging.getLogger("monumenten.api.cultureel_erfgoed")
|
|
9
|
+
|
|
10
|
+
_CULTUREEL_ERFGOED_SPARQL_ENDPOINT = (
|
|
11
|
+
"https://api.linkeddata.cultureelerfgoed.nl/datasets/rce/cho/sparql"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
_RIJKSMONUMENTEN_QUERY_TEMPLATE = """
|
|
15
|
+
PREFIX ceo:<https://linkeddata.cultureelerfgoed.nl/def/ceo#>
|
|
16
|
+
PREFIX bag:<http://bag.basisregistraties.overheid.nl/bag/id/>
|
|
17
|
+
PREFIX rn:<https://data.cultureelerfgoed.nl/term/id/rn/>
|
|
18
|
+
SELECT ?identificatie (MAX(?nummer) as ?rijksmonument_nummer)
|
|
19
|
+
WHERE {{
|
|
20
|
+
?monument ceo:heeftJuridischeStatus rn:b2d9a59a-fe1e-4552-9a05-3c2acddff864 ;
|
|
21
|
+
ceo:rijksmonumentnummer ?nummer ;
|
|
22
|
+
ceo:heeftBasisregistratieRelatie ?basisregistratieRelatie .
|
|
23
|
+
?basisregistratieRelatie ceo:heeftBAGRelatie ?bagRelatie .
|
|
24
|
+
?bagRelatie ceo:verblijfsobjectIdentificatie ?identificatie .
|
|
25
|
+
VALUES ?identificatie {{ {identificaties} }}
|
|
26
|
+
}}
|
|
27
|
+
GROUP BY ?identificatie
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
_BESCHERMDE_GEZICHTEN_QUERY = """
|
|
31
|
+
PREFIX ceo:<https://linkeddata.cultureelerfgoed.nl/def/ceo#>
|
|
32
|
+
PREFIX rn:<https://data.cultureelerfgoed.nl/term/id/rn/>
|
|
33
|
+
PREFIX geo: <http://www.opengis.net/ont/geosparql#>
|
|
34
|
+
SELECT DISTINCT ?gezicht ?beschermd_gezicht_naam ?gezichtWKT
|
|
35
|
+
WHERE {{
|
|
36
|
+
?gezicht
|
|
37
|
+
ceo:heeftGeometrie ?gezichtGeometrie ;
|
|
38
|
+
ceo:heeftGezichtsstatus rn:fd968529-bf70-4afa-8564-7c6c2fcfcc54;
|
|
39
|
+
ceo:heeftNaam/ceo:naam ?beschermd_gezicht_naam.
|
|
40
|
+
?gezichtGeometrie geo:asWKT ?gezichtWKT.
|
|
41
|
+
}}
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
_cultureel_erfgoed_semaphore: Optional[asyncio.Semaphore] = None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _get_semaphore(loop: asyncio.AbstractEventLoop) -> asyncio.Semaphore:
|
|
48
|
+
"""
|
|
49
|
+
Verkrijgt een semaphore voor het beperken van gelijktijdige aanvragen naar de Cultureel Erfgoed API.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
loop (asyncio.AbstractEventLoop): De asyncio event loop
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
asyncio.Semaphore: Een asyncio.Semaphore object dat het aantal gelijktijdige aanvragen beperkt tot 4
|
|
56
|
+
"""
|
|
57
|
+
global _cultureel_erfgoed_semaphore
|
|
58
|
+
if _cultureel_erfgoed_semaphore is None:
|
|
59
|
+
_cultureel_erfgoed_semaphore = asyncio.Semaphore(4)
|
|
60
|
+
return _cultureel_erfgoed_semaphore
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
async def _query_rijksmonumenten(
|
|
64
|
+
session: aiohttp.ClientSession, identificaties: List[str]
|
|
65
|
+
) -> List[Dict[str, Any]]:
|
|
66
|
+
"""
|
|
67
|
+
Voert een SPARQL-query uit om rijksmonumenten op te halen voor gegeven BAG-identificaties.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
session (aiohttp.ClientSession): De aiohttp ClientSession voor het uitvoeren van de HTTP-aanvraag
|
|
71
|
+
identificaties (List[str]): Lijst van BAG-identificaties waarvoor rijksmonumenten worden opgezocht
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
List[Dict[str, Any]]: Lijst van dictionaries met informatie over gevonden rijksmonumenten
|
|
75
|
+
|
|
76
|
+
Raises:
|
|
77
|
+
aiohttp.ClientResponseError: Bij fouten in de HTTP-aanvraag na 3 pogingen
|
|
78
|
+
"""
|
|
79
|
+
async with _get_semaphore(asyncio.get_running_loop()):
|
|
80
|
+
identificaties_str = " ".join(
|
|
81
|
+
f'"{identificatie}"' for identificatie in identificaties
|
|
82
|
+
)
|
|
83
|
+
query = _RIJKSMONUMENTEN_QUERY_TEMPLATE.format(
|
|
84
|
+
identificaties=identificaties_str
|
|
85
|
+
)
|
|
86
|
+
data = {"query": query, "format": "json"}
|
|
87
|
+
retries = 3
|
|
88
|
+
for poging in range(retries):
|
|
89
|
+
try:
|
|
90
|
+
async with session.post(
|
|
91
|
+
_CULTUREEL_ERFGOED_SPARQL_ENDPOINT, data=data
|
|
92
|
+
) as response:
|
|
93
|
+
response.raise_for_status()
|
|
94
|
+
resultaat = await response.json()
|
|
95
|
+
if isinstance(resultaat, list):
|
|
96
|
+
return resultaat
|
|
97
|
+
else:
|
|
98
|
+
logger.warning(
|
|
99
|
+
"Unexpected response format on attempt %d: %s",
|
|
100
|
+
poging + 1,
|
|
101
|
+
resultaat,
|
|
102
|
+
)
|
|
103
|
+
except aiohttp.ClientResponseError as e:
|
|
104
|
+
if poging != retries - 1:
|
|
105
|
+
logger.warning(
|
|
106
|
+
"Poging %d/%d voor rijksmonumenten query mislukt: %s. Opnieuw proberen over 1 seconde...",
|
|
107
|
+
poging + 1,
|
|
108
|
+
retries,
|
|
109
|
+
str(e),
|
|
110
|
+
)
|
|
111
|
+
await asyncio.sleep(1)
|
|
112
|
+
else:
|
|
113
|
+
raise
|
|
114
|
+
return List[Dict[str, Any]]()
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
async def _query_beschermde_gebieden(
|
|
118
|
+
session: aiohttp.ClientSession,
|
|
119
|
+
) -> List[Dict[str, Any]]:
|
|
120
|
+
"""
|
|
121
|
+
Voert een SPARQL-query uit om beschermde stads- en dorpsgezichten op te halen.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
session (aiohttp.ClientSession): De aiohttp ClientSession voor het uitvoeren van de HTTP-aanvraag
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
List[Dict[str, Any]]: Lijst van dictionaries met informatie over beschermde stads- en dorpsgezichten
|
|
128
|
+
|
|
129
|
+
Raises:
|
|
130
|
+
aiohttp.ClientResponseError: Bij fouten in de HTTP-aanvraag na 3 pogingen
|
|
131
|
+
"""
|
|
132
|
+
retries = 3
|
|
133
|
+
for poging in range(retries):
|
|
134
|
+
try:
|
|
135
|
+
data = {"query": _BESCHERMDE_GEZICHTEN_QUERY, "format": "json"}
|
|
136
|
+
|
|
137
|
+
async with session.post(
|
|
138
|
+
_CULTUREEL_ERFGOED_SPARQL_ENDPOINT, data=data
|
|
139
|
+
) as response:
|
|
140
|
+
response.raise_for_status()
|
|
141
|
+
resultaat = await response.json()
|
|
142
|
+
if isinstance(resultaat, list):
|
|
143
|
+
return resultaat
|
|
144
|
+
else:
|
|
145
|
+
logger.warning(
|
|
146
|
+
"Onverwacht response formaat bij poging %d: %s",
|
|
147
|
+
poging + 1,
|
|
148
|
+
resultaat,
|
|
149
|
+
)
|
|
150
|
+
except aiohttp.ClientResponseError as e:
|
|
151
|
+
if poging != retries - 1:
|
|
152
|
+
logger.warning(
|
|
153
|
+
"Poging %d/%d voor beschermde gebieden query mislukt: %s. Opnieuw proberen over 1 seconde...",
|
|
154
|
+
poging + 1,
|
|
155
|
+
retries,
|
|
156
|
+
str(e),
|
|
157
|
+
)
|
|
158
|
+
await asyncio.sleep(1)
|
|
159
|
+
else:
|
|
160
|
+
raise
|
|
161
|
+
return List[Dict[str, Any]]()
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Any, Dict, List, Optional
|
|
4
|
+
|
|
5
|
+
import aiohttp
|
|
6
|
+
|
|
7
|
+
_KADASTER_SPARQL_ENDPOINT = (
|
|
8
|
+
"https://api.labs.kadaster.nl/datasets/dst/kkg/services/default/sparql"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
_VERBLIJFSOBJECTEN_QUERY_TEMPLATE = """
|
|
12
|
+
PREFIX sor: <https://data.kkg.kadaster.nl/sor/model/def/>
|
|
13
|
+
PREFIX nen3610: <https://data.kkg.kadaster.nl/nen3610/model/def/>
|
|
14
|
+
PREFIX geo: <http://www.opengis.net/ont/geosparql#>
|
|
15
|
+
SELECT DISTINCT ?identificatie ?verblijfsobjectWKT
|
|
16
|
+
WHERE {{
|
|
17
|
+
?verblijfsobject sor:geregistreerdMet/nen3610:identificatie ?identificatie .
|
|
18
|
+
?verblijfsobject geo:hasGeometry/geo:asWKT ?verblijfsobjectWKT.
|
|
19
|
+
FILTER (?identificatie IN ( {identificaties} ))
|
|
20
|
+
}}
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
_kadaster_semaphore: Optional[asyncio.Semaphore] = None
|
|
24
|
+
|
|
25
|
+
# Create a module-level logger
|
|
26
|
+
logger = logging.getLogger("monumenten.api.kadaster")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _get_semaphore(loop: asyncio.AbstractEventLoop) -> asyncio.Semaphore:
|
|
30
|
+
global _kadaster_semaphore
|
|
31
|
+
if _kadaster_semaphore is None:
|
|
32
|
+
_kadaster_semaphore = asyncio.Semaphore(4)
|
|
33
|
+
return _kadaster_semaphore
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
async def _query_verblijfsobjecten(
|
|
37
|
+
session: aiohttp.ClientSession, identificaties: List[str]
|
|
38
|
+
) -> List[Dict[str, Any]]:
|
|
39
|
+
async with _get_semaphore(asyncio.get_running_loop()):
|
|
40
|
+
identificaties_str = ", ".join(
|
|
41
|
+
f'"{identificatie}"' for identificatie in identificaties
|
|
42
|
+
)
|
|
43
|
+
query = _VERBLIJFSOBJECTEN_QUERY_TEMPLATE.format(
|
|
44
|
+
identificaties=identificaties_str
|
|
45
|
+
)
|
|
46
|
+
data = {"query": query, "format": "json"}
|
|
47
|
+
retries = 3
|
|
48
|
+
for poging in range(retries):
|
|
49
|
+
try:
|
|
50
|
+
async with session.post(
|
|
51
|
+
_KADASTER_SPARQL_ENDPOINT, data=data
|
|
52
|
+
) as response:
|
|
53
|
+
response.raise_for_status()
|
|
54
|
+
resultaat = await response.json()
|
|
55
|
+
if isinstance(resultaat, list):
|
|
56
|
+
return resultaat
|
|
57
|
+
else:
|
|
58
|
+
logger.warning(
|
|
59
|
+
"Onverwacht response formaat bij poging %d: %s",
|
|
60
|
+
poging + 1,
|
|
61
|
+
resultaat,
|
|
62
|
+
)
|
|
63
|
+
except aiohttp.ClientResponseError as e:
|
|
64
|
+
if poging != retries - 1:
|
|
65
|
+
logger.warning(
|
|
66
|
+
"Poging %d/%d voor verblijfsobjecten query mislukt: %s. Opnieuw proberen over 1 seconde...",
|
|
67
|
+
poging + 1,
|
|
68
|
+
retries,
|
|
69
|
+
str(e),
|
|
70
|
+
)
|
|
71
|
+
await asyncio.sleep(1)
|
|
72
|
+
else:
|
|
73
|
+
logger.error(
|
|
74
|
+
"Alle pogingen voor verblijfsobjecten query mislukt: %s", str(e)
|
|
75
|
+
)
|
|
76
|
+
raise
|
|
77
|
+
return List[Dict[str, Any]]()
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""Interne processing functies voor de monumenten package."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from typing import Any, Dict, List, Tuple
|
|
5
|
+
|
|
6
|
+
import aiohttp
|
|
7
|
+
import geopandas as gpd
|
|
8
|
+
import pandas as pd
|
|
9
|
+
from tqdm.asyncio import tqdm_asyncio
|
|
10
|
+
|
|
11
|
+
from monumenten._api._cultureel_erfgoed import (
|
|
12
|
+
_query_beschermde_gebieden,
|
|
13
|
+
_query_rijksmonumenten,
|
|
14
|
+
)
|
|
15
|
+
from monumenten._api._kadaster import _query_verblijfsobjecten
|
|
16
|
+
|
|
17
|
+
_QUERY_BATCH_GROOTTE = 500 # lijkt meest optimaal qua performance
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
async def _verzamel_data(
|
|
21
|
+
session: aiohttp.ClientSession,
|
|
22
|
+
identificaties_batch: List[str],
|
|
23
|
+
) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]:
|
|
24
|
+
"""Verzamel data van verschillende API's voor een batch verblijfsobjecten.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
session (aiohttp.ClientSession): De sessie voor HTTP requests
|
|
28
|
+
identificaties_batch (List[str]): Lijst met verblijfsobject ID's
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]: Tuple met rijksmonumenten en verblijfsobjecten data
|
|
32
|
+
"""
|
|
33
|
+
rijksmonumenten_taak = _query_rijksmonumenten(session, identificaties_batch)
|
|
34
|
+
verblijfsobjecten_taak = _query_verblijfsobjecten(session, identificaties_batch)
|
|
35
|
+
return await asyncio.gather(rijksmonumenten_taak, verblijfsobjecten_taak)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
async def _process_batch(
|
|
39
|
+
session: aiohttp.ClientSession, batch: List[str], bg_df: gpd.GeoDataFrame
|
|
40
|
+
) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]], int]:
|
|
41
|
+
"""Verwerk een batch verblijfsobjecten.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
session (aiohttp.ClientSession): De sessie voor HTTP requests
|
|
45
|
+
batch (List[str]): Lijst met verblijfsobject ID's
|
|
46
|
+
bg_df (gpd.GeoDataFrame): GeoDataFrame met beschermde gezichten
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Tuple[List[Dict[str, Any]], List[Dict[str, Any]], int]: Tuple met rijksmonumenten,
|
|
50
|
+
beschermde gezichten en aantal verwerkte objecten
|
|
51
|
+
"""
|
|
52
|
+
# Get the current event loop
|
|
53
|
+
loop = asyncio.get_running_loop()
|
|
54
|
+
|
|
55
|
+
# Create tasks using the current loop
|
|
56
|
+
rijksmonumenten_taak = loop.create_task(_query_rijksmonumenten(session, batch))
|
|
57
|
+
verblijfsobjecten_taak = loop.create_task(_query_verblijfsobjecten(session, batch))
|
|
58
|
+
|
|
59
|
+
# Wait for both tasks to complete
|
|
60
|
+
rijksmonumenten, verblijfsobjecten = await asyncio.gather(
|
|
61
|
+
rijksmonumenten_taak, verblijfsobjecten_taak
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Process 'verblijfsobjecten' into GeoDataFrame
|
|
65
|
+
if verblijfsobjecten:
|
|
66
|
+
vo_df = pd.DataFrame(verblijfsobjecten)
|
|
67
|
+
vo_df["geometry"] = gpd.GeoSeries.from_wkt(vo_df["verblijfsobjectWKT"])
|
|
68
|
+
vo_df = gpd.GeoDataFrame(
|
|
69
|
+
vo_df[["identificatie", "geometry"]], geometry="geometry"
|
|
70
|
+
)
|
|
71
|
+
else:
|
|
72
|
+
vo_df = gpd.GeoDataFrame(columns=["identificatie", "geometry"])
|
|
73
|
+
|
|
74
|
+
# Perform spatial join
|
|
75
|
+
if not vo_df.empty:
|
|
76
|
+
joined_df = gpd.sjoin(vo_df, bg_df, how="left", predicate="within")
|
|
77
|
+
|
|
78
|
+
verblijfsobjecten_in_beschermd_gezicht = joined_df[
|
|
79
|
+
["identificatie", "beschermd_gezicht_naam"]
|
|
80
|
+
].to_dict("records")
|
|
81
|
+
else:
|
|
82
|
+
verblijfsobjecten_in_beschermd_gezicht = List[Dict[str, Any]]()
|
|
83
|
+
|
|
84
|
+
return rijksmonumenten, verblijfsobjecten_in_beschermd_gezicht, len(batch)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
async def _query(
|
|
88
|
+
session: aiohttp.ClientSession, verblijfsobject_ids: List[str]
|
|
89
|
+
) -> pd.DataFrame:
|
|
90
|
+
"""Voer queries uit voor een lijst verblijfsobjecten.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
session (aiohttp.ClientSession): De sessie voor HTTP requests
|
|
94
|
+
verblijfsobject_ids (List[str]): Lijst met verblijfsobject ID's
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
pd.DataFrame: DataFrame met monumentinformatie
|
|
98
|
+
"""
|
|
99
|
+
# Load 'beschermde_gebieden' and convert to GeoDataFrame
|
|
100
|
+
beschermde_gebieden = await _query_beschermde_gebieden(session)
|
|
101
|
+
bg_df = gpd.GeoDataFrame()
|
|
102
|
+
|
|
103
|
+
if beschermde_gebieden:
|
|
104
|
+
bg_df = pd.DataFrame(beschermde_gebieden)
|
|
105
|
+
bg_df["geometry"] = gpd.GeoSeries.from_wkt(bg_df["gezichtWKT"])
|
|
106
|
+
bg_df = gpd.GeoDataFrame(
|
|
107
|
+
bg_df[["beschermd_gezicht_naam", "geometry"]], geometry="geometry"
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Ensure spatial index is built
|
|
111
|
+
bg_df.sindex
|
|
112
|
+
|
|
113
|
+
rijksmonumenten_result = list[Dict[str, Any]]()
|
|
114
|
+
verblijfsobjecten_in_beschermd_gezicht_result = list[Dict[str, Any]]()
|
|
115
|
+
|
|
116
|
+
# Prepare batches
|
|
117
|
+
batches = [
|
|
118
|
+
verblijfsobject_ids[i : i + _QUERY_BATCH_GROOTTE]
|
|
119
|
+
for i in range(0, len(verblijfsobject_ids), _QUERY_BATCH_GROOTTE)
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
# Create tasks for each batch
|
|
123
|
+
tasks = [_process_batch(session, batch, bg_df) for batch in batches]
|
|
124
|
+
|
|
125
|
+
progress_bar = tqdm_asyncio(total=len(verblijfsobject_ids))
|
|
126
|
+
|
|
127
|
+
for task in asyncio.as_completed(tasks):
|
|
128
|
+
result = await task
|
|
129
|
+
rijksmonumenten_result.extend(result[0])
|
|
130
|
+
verblijfsobjecten_in_beschermd_gezicht_result.extend(result[1])
|
|
131
|
+
progress_bar.update(result[2])
|
|
132
|
+
|
|
133
|
+
progress_bar.close()
|
|
134
|
+
|
|
135
|
+
df_rijksmonumenten = (
|
|
136
|
+
pd.DataFrame(rijksmonumenten_result)
|
|
137
|
+
if rijksmonumenten_result
|
|
138
|
+
else pd.DataFrame({"identificatie": [], "rijksmonument_nummer": []})
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
df_beschermd_gezicht = (
|
|
142
|
+
pd.DataFrame(verblijfsobjecten_in_beschermd_gezicht_result)
|
|
143
|
+
if verblijfsobjecten_in_beschermd_gezicht_result
|
|
144
|
+
else pd.DataFrame({"identificatie": [], "beschermd_gezicht_naam": []})
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
result = pd.merge(
|
|
148
|
+
df_rijksmonumenten, df_beschermd_gezicht, on="identificatie", how="outer"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
return result
|
monumenten/_version.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# file generated by setuptools_scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
TYPE_CHECKING = False
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from typing import Tuple, Union
|
|
6
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
7
|
+
else:
|
|
8
|
+
VERSION_TUPLE = object
|
|
9
|
+
|
|
10
|
+
version: str
|
|
11
|
+
__version__: str
|
|
12
|
+
__version_tuple__: VERSION_TUPLE
|
|
13
|
+
version_tuple: VERSION_TUPLE
|
|
14
|
+
|
|
15
|
+
__version__ = version = '0.1.0'
|
|
16
|
+
__version_tuple__ = version_tuple = (0, 1, 0)
|
monumenten/client.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Client voor monumenten package."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Optional
|
|
4
|
+
|
|
5
|
+
import aiohttp
|
|
6
|
+
import pandas as pd
|
|
7
|
+
|
|
8
|
+
from monumenten._processing import _query
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class MonumentenClient:
|
|
12
|
+
"""Client voor het ophalen van monumentgegevens van verschillende Nederlandse overheids-API's.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
session (Optional[aiohttp.ClientSession]): Optionele aiohttp.ClientSession. Indien niet opgegeven wordt
|
|
16
|
+
een nieuwe sessie aangemaakt en beheerd door de client.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, session: Optional[aiohttp.ClientSession] = None) -> None:
|
|
20
|
+
self._session = session
|
|
21
|
+
self._owns_session = session is None
|
|
22
|
+
|
|
23
|
+
async def __aenter__(self) -> "MonumentenClient":
|
|
24
|
+
if self._owns_session:
|
|
25
|
+
self._session = aiohttp.ClientSession()
|
|
26
|
+
return self
|
|
27
|
+
|
|
28
|
+
async def __aexit__(
|
|
29
|
+
self,
|
|
30
|
+
exc_type: Optional[type[BaseException]],
|
|
31
|
+
exc_val: Optional[BaseException],
|
|
32
|
+
exc_tb: Optional[Any],
|
|
33
|
+
) -> None:
|
|
34
|
+
if self._owns_session and self._session:
|
|
35
|
+
await self._session.close()
|
|
36
|
+
|
|
37
|
+
async def process_from_df(
|
|
38
|
+
self,
|
|
39
|
+
df: pd.DataFrame,
|
|
40
|
+
verblijfsobject_id_col: str,
|
|
41
|
+
) -> pd.DataFrame:
|
|
42
|
+
"""Verwerk een DataFrame met verblijfsobject ID's.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
df (pd.DataFrame): Input DataFrame met verblijfsobject ID's
|
|
46
|
+
verblijfsobject_id_col (str): Naam van de kolom met de verblijfsobject ID's
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
pd.DataFrame: DataFrame met toegevoegde monumentinformatie
|
|
50
|
+
|
|
51
|
+
Raises:
|
|
52
|
+
RuntimeError: Als de client niet als context manager wordt gebruikt
|
|
53
|
+
"""
|
|
54
|
+
if not self._session:
|
|
55
|
+
raise RuntimeError("Client must be used as a context manager")
|
|
56
|
+
|
|
57
|
+
results = await _query(
|
|
58
|
+
self._session, df[verblijfsobject_id_col].drop_duplicates()
|
|
59
|
+
)
|
|
60
|
+
merged = pd.merge(
|
|
61
|
+
df, results, left_on=verblijfsobject_id_col, right_on="identificatie"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
if "identificatie" not in df.columns:
|
|
65
|
+
merged = merged.drop(columns=["identificatie"])
|
|
66
|
+
|
|
67
|
+
rijksmonument_nummer_position = merged.columns.get_loc("rijksmonument_nummer")
|
|
68
|
+
merged.insert(
|
|
69
|
+
rijksmonument_nummer_position + 1,
|
|
70
|
+
"rijksmonument_url",
|
|
71
|
+
"https://monumenten.nl/monument/"
|
|
72
|
+
+ merged["rijksmonument_nummer"]
|
|
73
|
+
.fillna("")
|
|
74
|
+
.astype(str)
|
|
75
|
+
.where(merged["rijksmonument_nummer"].notna(), None),
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
merged.insert(
|
|
79
|
+
rijksmonument_nummer_position,
|
|
80
|
+
"is_rijksmonument",
|
|
81
|
+
merged["rijksmonument_nummer"].notna(),
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
beschermd_gezicht_naam_position = merged.columns.get_loc(
|
|
85
|
+
"beschermd_gezicht_naam"
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
merged.insert(
|
|
89
|
+
beschermd_gezicht_naam_position,
|
|
90
|
+
"is_beschermd_gezicht",
|
|
91
|
+
merged["beschermd_gezicht_naam"].notna(),
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
return merged
|
|
95
|
+
|
|
96
|
+
async def process_from_list(
|
|
97
|
+
self, verblijfsobject_ids: List[str]
|
|
98
|
+
) -> List[Dict[str, Any]]:
|
|
99
|
+
"""Verwerk een lijst met verblijfsobject ID's.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
verblijfsobject_ids (List[str]): Lijst met te verwerken ID's
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
List[Dict[str, Any]]: Lijst met dictionaries met monumentinformatie
|
|
106
|
+
"""
|
|
107
|
+
df = pd.DataFrame({"bag_verblijfsobject_id": verblijfsobject_ids})
|
|
108
|
+
result = await self.process_from_df(df, "bag_verblijfsobject_id")
|
|
109
|
+
result = result.replace({pd.NA: None, pd.NaT: None})
|
|
110
|
+
return list(result.to_dict(orient="records"))
|
monumenten/logging.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Woonstad Rotterdam
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: monumenten
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Checkt op basis van BAG verblijfsobject-ids of het verblijfsobject een monument en/of beschermd stadsgezicht is.
|
|
5
|
+
Author-email: Woonstad Rotterdam <info@woonstadrotterdam.nl>, Ben Verhees <ben.verhees@woonstadrotterdam.nl>, Tomer Gabay <tomer.gabay@woonstadrotterdam.nl>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2024 Woonstad Rotterdam
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/woonstadrotterdam/monumenten
|
|
29
|
+
Project-URL: Issues, https://github.com/woonstadrotterdam/monumenten/issues
|
|
30
|
+
Keywords: monument,bag,stadsgezicht,monumentenregister,beschermd
|
|
31
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
32
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
33
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
34
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
35
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
36
|
+
Classifier: Operating System :: OS Independent
|
|
37
|
+
Requires-Python: >=3.9.5
|
|
38
|
+
Description-Content-Type: text/markdown
|
|
39
|
+
License-File: LICENSE
|
|
40
|
+
Requires-Dist: aiohttp>=3.10.10
|
|
41
|
+
Requires-Dist: Shapely>=2.0.6
|
|
42
|
+
Requires-Dist: tqdm>=4.66.5
|
|
43
|
+
Requires-Dist: pandas>=2.0.0
|
|
44
|
+
Requires-Dist: geopandas>=1.0.1
|
|
45
|
+
Provides-Extra: dev
|
|
46
|
+
Requires-Dist: monumenten[test]; extra == "dev"
|
|
47
|
+
Requires-Dist: ruff==0.4.8; extra == "dev"
|
|
48
|
+
Provides-Extra: test
|
|
49
|
+
Requires-Dist: pre-commit==3.*; extra == "test"
|
|
50
|
+
Requires-Dist: pytest==8.*; extra == "test"
|
|
51
|
+
Requires-Dist: pytest-cov==5.*; extra == "test"
|
|
52
|
+
Requires-Dist: pytest-asyncio==0.24.*; extra == "test"
|
|
53
|
+
|
|
54
|
+
# Monumenten
|
|
55
|
+
|
|
56
|
+
Een Python package voor het ophalen van monumentgegevens van Nederlandse overheids-API's. Momenteel is het mogelijk om rijksmonumenten en beschermd gezichten op te halen.
|
|
57
|
+
|
|
58
|
+
Door middel van dit package is het mogelijk om, indienst gewenst, voor tienduizenden verblijfsobjecten per seconde monumentgegevens op te halen. Er zijn geen API-keys nodig.
|
|
59
|
+
|
|
60
|
+
## Installatie
|
|
61
|
+
|
|
62
|
+
`pip install monumenten`
|
|
63
|
+
|
|
64
|
+
## Gebruik
|
|
65
|
+
|
|
66
|
+
### Python-native 🐍
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
import asyncio
|
|
70
|
+
|
|
71
|
+
from monumenten import MonumentenClient
|
|
72
|
+
|
|
73
|
+
async def main():
|
|
74
|
+
bag_verblijfsobject_ids = [
|
|
75
|
+
"0599010000360091",
|
|
76
|
+
"0599010000486642",
|
|
77
|
+
"0599010000281115",
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
async with MonumentenClient() as client:
|
|
81
|
+
result = await client.process_from_list(bag_verblijfsobject_ids)
|
|
82
|
+
print(result)
|
|
83
|
+
|
|
84
|
+
# in een .py file"
|
|
85
|
+
if __name__ == "__main__":
|
|
86
|
+
asyncio.run(main())
|
|
87
|
+
|
|
88
|
+
# in een .ipynb file (notebook):
|
|
89
|
+
await main()
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
# OUTPUT
|
|
94
|
+
[
|
|
95
|
+
{
|
|
96
|
+
'bag_verblijfsobject_id': '0599010000360091',
|
|
97
|
+
'is_rijksmonument': True,
|
|
98
|
+
'rijksmonument_nummer': '524327',
|
|
99
|
+
'rijksmonument_url': 'https://monumenten.nl/monument/524327',
|
|
100
|
+
'is_beschermd_gezicht': False,
|
|
101
|
+
'beschermd_gezicht_naam': None
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
'bag_verblijfsobject_id': '0599010000486642',
|
|
105
|
+
'is_rijksmonument': False,
|
|
106
|
+
'rijksmonument_nummer': None,
|
|
107
|
+
'rijksmonument_url': None,
|
|
108
|
+
'is_beschermd_gezicht': False,
|
|
109
|
+
'beschermd_gezicht_naam': None
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
'bag_verblijfsobject_id': '0599010000281115',
|
|
113
|
+
'is_rijksmonument': False,
|
|
114
|
+
'rijksmonument_nummer': None,
|
|
115
|
+
'rijksmonument_url': None,
|
|
116
|
+
'is_beschermd_gezicht': True,
|
|
117
|
+
'beschermd_gezicht_naam': 'Kralingen - Midden'
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Met pandas 🐼
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
import asyncio
|
|
126
|
+
|
|
127
|
+
import pandas as pd
|
|
128
|
+
from monumenten import MonumentenClient
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
async def main():
|
|
132
|
+
|
|
133
|
+
input_df = pd.DataFrame(
|
|
134
|
+
{
|
|
135
|
+
"bag_verblijfsobject_id": [
|
|
136
|
+
"0599010000360091",
|
|
137
|
+
"0599010000486642",
|
|
138
|
+
"0599010000360022",
|
|
139
|
+
"0599010000360096",
|
|
140
|
+
"0599010000183527",
|
|
141
|
+
"0599010400025880",
|
|
142
|
+
"0599010000281115",
|
|
143
|
+
]
|
|
144
|
+
}
|
|
145
|
+
) # of lees van een csv of een ander bestand in
|
|
146
|
+
|
|
147
|
+
async with MonumentenClient() as client:
|
|
148
|
+
result = await client.process_from_df(
|
|
149
|
+
df=input_df, verblijfsobject_id_col="bag_verblijfsobject_id"
|
|
150
|
+
)
|
|
151
|
+
result.to_csv("monumenten.csv", index=False)
|
|
152
|
+
|
|
153
|
+
# in een .py file"
|
|
154
|
+
if __name__ == "__main__":
|
|
155
|
+
asyncio.run(main())
|
|
156
|
+
|
|
157
|
+
# in een .ipynb file (notebook):
|
|
158
|
+
await main()
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
| bag_verblijfsobject_id | is_rijksmonument | rijksmonument_nummer | rijksmonument_url | is_beschermd_gezicht | beschermd_gezicht_naam |
|
|
162
|
+
| ---------------------- | ---------------- | -------------------- | ------------------------------------- | -------------------- | ------------------------------- |
|
|
163
|
+
| 0599010000360091 | True | 524327 | https://monumenten.nl/monument/524327 | False | |
|
|
164
|
+
| 0599010000486642 | False | | | False | |
|
|
165
|
+
| 0599010000360022 | True | 524327 | https://monumenten.nl/monument/524327 | False | |
|
|
166
|
+
| 0599010000360096 | True | 524327 | https://monumenten.nl/monument/524327 | False | |
|
|
167
|
+
| 0599010000183527 | True | 32807 | https://monumenten.nl/monument/32807 | True | Rotterdam - Scheepvaartkwartier |
|
|
168
|
+
| 0599010400025880 | False | | | False | |
|
|
169
|
+
| 0599010000281115 | False | | | True | Kralingen - Midden |
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
monumenten/__init__.py,sha256=RsPjyAQJ3mi43ICefZQUEnqqWrwXk9xQXIq_VxnQwKk,149
|
|
2
|
+
monumenten/_processing.py,sha256=mSionFmOoIbQxmcASiDjRmCeOD_aib1EefDSwkFCsYg,5292
|
|
3
|
+
monumenten/_version.py,sha256=IMl2Pr_Sy4LVRKy_Sm4CdwUl1Gryous6ncL96EMYsnM,411
|
|
4
|
+
monumenten/client.py,sha256=6VsQbxpf_aMeEfYQVT3guFGSCOFGm6r4SGcqic0nd0Q,3585
|
|
5
|
+
monumenten/logging.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
monumenten/_api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
monumenten/_api/_cultureel_erfgoed.py,sha256=zJkkhM1xUrBFOJ16eVfrPv071vCHuc4IDXO_Dz4b8EI,5975
|
|
8
|
+
monumenten/_api/_kadaster.py,sha256=8aBJ4Et2QDpJuxq-CX2wX4z10MYsLhHhZHq5J2no6Sw,2808
|
|
9
|
+
monumenten-0.1.0.dist-info/LICENSE,sha256=XHXrWnYTM8HAM7azt1zAhsP6aD-sM-2DPoUAfoYx57Y,1075
|
|
10
|
+
monumenten-0.1.0.dist-info/METADATA,sha256=GRhssvq7fAODyiCGga_LflQoWmAL6xYY61HcKTZj55M,6795
|
|
11
|
+
monumenten-0.1.0.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
|
|
12
|
+
monumenten-0.1.0.dist-info/top_level.txt,sha256=evniMq34R8O_etXp8lHeRW7ESy32fbZvjUmDtMrLg2s,11
|
|
13
|
+
monumenten-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
monumenten
|