Ik wil graag de kinderopvanglocaties in kaart brengen voor heel Nederland (ivb analyse van voorzieningen voor stedenbouwkundige projecten).
Deze locaties zijn beschikbaar als CSV op Gegevens kinderopvanglocaties LRK | Data overheid
In het csv-bestand heb je velden over het adres, de postcode en de gemeente van de kinderopvanglocaties.
Ik wilde ze met de locatieserver geocoderen naar punten door een Python script (30 000 locaties), maar ik kreeg een foutmelding “Max retries exceeded with url”.
Ik kan me voorstellen dat ik buiten de “fair-use” policy ben gevallen. Ik voeg graag (lange) vertraging tussen aanvragen toe, maar ik den niet dat het een oplossing is.
Is de locatieserver voor degelijke use-case bedoeld?
Ik kan anders gewoon Nominatim gebruiken ipv de PDOK-locatieserver…
Ik heb mijn Python code hieronder geplaakt.
Voorbeeld API call
q=adres:Zocherstraat 68 AND postcode:1054MA AND gemeente:Amsterdam
rows=1
fl=id centroide_rd score identificatie
# made with the help of Claude Haiku 3.5 AI model through Duck.ai
def geolocate_addresses(input_df, field_mapping=None):
"""
Geolocate addresses using PDOK Locatieserver API met veldnaam-specifiek zoeken
Parameters:
-----------
input_df : pandas.DataFrame
DataFrame met adresgegevens
field_mapping : dict, optional
Mapping van zoekvelden voor meer nauwkeurige geocoding
Returns:
--------
geopandas.GeoDataFrame
Geodataframe met geometrie
"""
# Standaard veld mapping indien niet gespecificeerd
if field_mapping is None:
field_mapping = {
'adres': 'adres',
'postcode': 'postcode',
'gemeente': 'gemeente'
}
# Lijst voor opgeslagen resultaten
geolocated_data = []
# API endpoint
api_url = "https://geodata.nationaalgeoregister.nl/locatieserver/v3/free"
# Itereer door elke rij in de input dataframe
for index, row in input_df.iterrows():
# Construct de zoekquery met veldspecifieke zoektermen
search_terms = []
for field, api_field in field_mapping.items():
if field in row and row[field]:
# Voeg veldnaam toe aan zoekterm
search_terms.append(f"{api_field}:{row[field]}")
# Combineer zoektermen
search_query = " AND ".join(search_terms)
# Parameters voor de API aanvraag
params = {
'q': search_query,
'rows': 1, # Meest relevante resultaat
'fl': 'id centroide_rd score identificatie'
}
try:
# Doe de API aanvraag
response = requests.get(api_url, params=params)
response.raise_for_status()
# Verwerk de JSON response
data = response.json()
# Controleer of er resultaten zijn
if data['response']['numFound'] > 0:
# Pak het eerste (meest relevante) resultaat
result = data['response']['docs'][0]
# Extraheer coördinaten
if 'centroide_rd' in result:
# Maak een nieuwe dictionary met originele data en geometrie
geo_row = row.to_dict()
geo_row['geometry'] = shapely.wkt.loads(result['centroide_rd'])
# Extra metadata toevoegen voor verificatie
geo_row['_score'] = result.get('score', 0)
geo_row['_identificatie'] = result.get('identificatie', 'Geen')
geolocated_data.append(geo_row)
else:
print(f"Geen coördinaten gevonden voor: {search_query}")
else:
print(f"Geen resultaten gevonden voor: {search_query}")
except requests.RequestException as e:
print(f"Fout bij geoloceren {search_query}: {e}")
time.sleep(60)
# Converteer naar GeoDataFrame
gdf = gpd.GeoDataFrame(geolocated_data, geometry='geometry')
return gdf
30 duizend keer een API aanroepen is wellicht wat te.
BAG heeft ook adresinformatie, dus je kan dat ook makkelijk lokaal doen.
Als je een Python environment maakt met geopandas en duckdb geïnstalleerd en in dezelfde directory als onderstaande python script ook de export_opendata_lrk.csv en postcode.parquet bestanden hebt (die laatste kan je hier downloaden) dan geeft dit je in een paar seconden een geopackage (lrk.gpkg) met alle locaties in RD New (EPSG:28992).
Even los van de 3182 die geen adres/postcode hebben (vanwege privacy?) lijken er 81 adressen te zijn die niet gevonden kunnen worden.
#! /usr/bin/env python3
import geopandas as gpd
import duckdb
def main():
db = duckdb.connect()
db.execute(f"INSTALL spatial; LOAD spatial;")
db.execute("""
CREATE VIEW lrk AS
SELECT *
FROM read_csv('export_opendata_lrk.csv', delim=';', encoding='UTF-8',ignore_errors=true);
""")
db.execute("""
CREATE VIEW pc AS
SELECT * REPLACE(ST_Transform(loc, 'EPSG:4326', 'EPSG:28992', true) AS loc)
FROM 'postcode.parquet';
""")
db.execute("""
CREATE VIEW lrk_loc AS
SELECT
l.*, coalesce(St_AsText(pc.loc),'POINT EMPTY') AS loc
FROM lrk l
LEFT JOIN pc
ON upper(l.opvanglocatie_adres)=upper(concat(straat,' ',huisnummer,huisletter,' ',toevoeging))
AND l.opvanglocatie_postcode=pc.postcode;
""");
df = db.execute("SELECT * FROM lrk_loc;").fetch_df();
gdf = gpd.GeoDataFrame(df)
gdf["loc"] = gpd.GeoSeries.from_wkt(gdf["loc"])
gdf = gdf.set_geometry("loc", crs="EPSG:28992")
gdf.to_file("lrk.gpkg",
layer= "lrk",
driver="GPKG")
if __name__ == '__main__':
# Run the main process
main()
Of in de broncode daarvan kijken hoe Richard (de maker vd plugin) met de “max retries” omgaat.
Een andere route: de CSV met opvanglocaties bevat ook een BAG-id. Die verwijst naar het veld “identificatie” in de tabel “verblijfsobject” in de BAG, (waar ook de locatie in zit). Dan ben je er ook.
Tip. Ik zie dat sommige kinderopvanglocaties er dubbel inzitten, nl. als KDV én als DSO. Iets om rekeningmee te houden met je analyse!
De locatieserver is vooral bedoeld voor autocomplete. De geometrieën die de locatieserver teruggeeft zijn ook gegeneraliseerd en niet de originele geometrieën. Als je een analyse wilt doen zoals je nu wilt doen hebben we momenteel een geopackage beschikbaar ter download in de bag atom: BAG. Dan kan je het bestand in een keer downloaden en daar je analyse op doen. Dat is op dit moment het meest geschikte koppelvlak om deze analyse op te doen.
Er zitten echter een aantal ontwikkelingen aan te komen, waarvan een op korte termijn: de location API.
Momenteel zijn we bezig met het herzien van de locatieserver, de Location API. Die zal zich allereerst richten op het aanbieden van een autocomplete, waarbij een gelinkt wordt naar features in OGC API’s (ipv alles via een service te ontsluiten zoals bij de locatieservice). In dat kader zijn we ook bezig met het toevoegen van filtering in de onderliggende OGC API’s, waaronder die van de BAG. Dit is nog in ontwikkeling en hierbij zullen we rekening houden met de performance vereisten aan de API’s met welke filtering uiteindelijk mogelijk is.
Dit jaar is PDOK tevens van plan om in de OGC API bulk downloads mogelijk te maken zoals geopackages.
Hartelijk bedankt voor alle jullie (snele) antwoorden!
Omdat het veld ‘postcode’ de meest precieze info heeft in het CSV-bestand van de LRK, denk ik dat de methode van @remcopoortinga de beste en snelste is.
Koppeling met BAG-id was een goeie tip van @gisnederland maar ik vind alleen ~10k matches uit de ~28,5k rijen met een BAG-id
–
We hebben eigenlijk in onze database een dataset die de BAG verblijsfobjecten, nummeraanduiding, openbareruimte en woonplaats tabellen combineerd.
Ik kon het dus in onze database gemakkelijk al doen met een table join…
Maar nu heb ik meer dingen geleerd!
–
De volgende join clause heeft een match gevonden voor 27800 van de 28708 rijen met een adres. Er bleven ~900 locaties over (vanwege encodering fouten of adres spelfoutjes), die ik op postcode niveau goed geocoderen kan (met de methode van Remco).
Er bleeft nog 1 adres met een spelfoutje die ik kon controleren met de website gedeeld door @Anouk. Volgens mij en P ipv S in de postcode letters. Ik heb een emailtje gestuurd naar gegevensleveringko@duo.nl, aanbieder van het LRK-CSV…
Ik denk dat je wat te veel ‘trimt’ in je join clause, en dat je daardoor minder matches hebt (ik had maar 81 die niets opleverden). Ik vermoed zelfs dat de opvanglocatie_adres in de CSV is afgeleid uit de BAG (met dat bag_id) met een simpele concat(straat,' ',huisnummer,huisletter,' ', toevoeging), omdat adressen die geen toevoeging hebben eindigen met een spatie in de CSV en adressen met een toevoeging niet
Eigenlijk zou een WMS/WFS gewoon basis moeten zijn op deze dataset. Heb ooit geprobeerd om de bronhouders (LRK, sociale zaken, onderwijs etc.) een kaart/geowebservice te laten maken (lijkt me nogal logisch). Dat werd van het kastje naar de muur en uiteindelijk niets…
Een alternatief is om in Q-GIS een vector data join met van de BAG verblijfsobjecten (geopackage) met de LRK (CSV). Het resultaat kan je zelf hosten in bijvoorbeeld geoserver.