From a7ecfd0d1fa04846435bc23d25188a0297e54b67 Mon Sep 17 00:00:00 2001 From: Jan Derk Date: Tue, 4 Jul 2023 17:11:50 +0200 Subject: [PATCH] V58 - Nevenadressen are now parsed. They are stored in a nevenadressen table. - Adressen table has a new hoofd_nummer_id field. If filled, points to the hoofdadres. - Parser now handles multiple field values. For example, a verblijfsobject can contain multiple nevenadressen. - Updated progress bar data - Renamed "openbareruimte_id" field in nummers table to "openbare_ruimte_id" - More logging when creating adressen table. --- bag/bag_parser.py | 43 +++++----- config.py | 6 +- database_sqlite/database_sqlite.py | 133 +++++++++++++++++++++-------- import_bag.py | 1 - readme.MD | 48 +++++++---- 5 files changed, 155 insertions(+), 76 deletions(-) diff --git a/bag/bag_parser.py b/bag/bag_parser.py index 8c65923..bc54257 100644 --- a/bag/bag_parser.py +++ b/bag/bag_parser.py @@ -46,7 +46,7 @@ def parse(self, tag_name): self.object_tag_name = ns_objecten + tag_name self.file_bag_code = "9999WPL" - self.total_xml = 3889 # required for progress indicator. Actual numbers can be found in the console or log. + self.total_xml = 4049 # required for progress indicator. Actual numbers can be found in the console or log. self.data_init = {'geometry': ''} self.save_to_database = self.__save_woonplaats self.db_fields = { @@ -65,7 +65,7 @@ def parse(self, tag_name): self.object_tag_name = ns_gwr_product + tag_name self.file_bag_code = "GEM-WPL-RELATIE" - self.total_xml = 5765 # required for progress indicator + self.total_xml = 5773 # required for progress indicator self.save_to_database = self.__save_gemeente_woonplaats self.db_fields = { @@ -86,9 +86,9 @@ def parse(self, tag_name): self.object_tag_name = ns_objecten + tag_name self.file_bag_code = "9999OPR" - self.total_xml = 343448 # required for progress indicator + self.total_xml = 346970 # required for progress indicator self.data_init = {'verkorte_naam': ''} - self.save_to_database = self.__save_openbareruimte + self.save_to_database = self.__save_openbare_ruimte self.db_fields = { ns_objecten + 'identificatie': 'id', @@ -109,7 +109,7 @@ def parse(self, tag_name): self.object_tag_name = ns_objecten + tag_name self.file_bag_code = "9999NUM" - self.total_xml = 12054045 # required for progress indicator + self.total_xml = 12287165 # required for progress indicator # Initialization required as BAG leaves fields out of the data if it is empty self.data_init = {'huisletter': '', 'toevoeging': '', 'postcode': '', 'woonplaats_id': ''} self.save_to_database = self.__save_nummer @@ -123,7 +123,7 @@ def parse(self, tag_name): ns_historie + 'beginGeldigheid': 'begindatum_geldigheid', ns_historie + 'eindGeldigheid': 'einddatum_geldigheid', ns_objecten + 'status': 'status', - ns_objecten_ref + 'OpenbareRuimteRef': 'openbareruimte_id', + ns_objecten_ref + 'OpenbareRuimteRef': 'openbare_ruimte_id', ns_objecten_ref + 'WoonplaatsRef': 'woonplaats_id', } self.db_tag_parent_fields = {} @@ -134,7 +134,7 @@ def parse(self, tag_name): self.object_tag_name = ns_objecten + tag_name self.file_bag_code = "9999PND" - self.total_xml = 20352252 # required for progress indicator + self.total_xml = 21286109 # required for progress indicator self.data_init = {'geometry': ''} self.save_to_database = self.__save_pand @@ -157,8 +157,8 @@ def parse(self, tag_name): self.object_tag_name = ns_objecten + tag_name self.file_bag_code = "9999VBO" - self.total_xml = 21148447 # required for progress indicator - self.data_init = {'pos': '', 'rd_x': '', 'rd_y': '', 'latitude': '', 'longitude': ''} + self.total_xml = 22552963 # required for progress indicator + self.data_init = {'pos': '', 'rd_x': '', 'rd_y': '', 'latitude': '', 'longitude': '', 'nevenadressen': ''} self.save_to_database = self.__save_verblijfsobject self.db_fields = { @@ -175,6 +175,7 @@ def parse(self, tag_name): # Therefore, identification is done by combining the tag with the parent tag self.db_tag_parent_fields = { ns_objecten + 'heeftAlsHoofdadres' + ns_objecten_ref + 'NummeraanduidingRef': 'nummer_id', + ns_objecten + 'heeftAlsNevenadres' + ns_objecten_ref + 'NummeraanduidingRef': 'nevenadressen', } elif self.tag_name == 'Ligplaats': ns_objecten = "{www.kadaster.nl/schemas/lvbag/imbag/objecten/v20200601}" @@ -184,7 +185,7 @@ def parse(self, tag_name): self.object_tag_name = ns_objecten + tag_name self.file_bag_code = "9999LIG" - self.total_xml = 17653 # required for progress indicator + self.total_xml = 18131 # required for progress indicator self.data_init = {'pos': '', 'rd_x': '', 'rd_y': '', 'latitude': '', 'longitude': '', 'geometry': ''} self.save_to_database = self.__save_ligplaats @@ -210,7 +211,7 @@ def parse(self, tag_name): self.object_tag_name = ns_objecten + tag_name self.file_bag_code = "9999STA" - self.total_xml = 49543 # required for progress indicator + self.total_xml = 56684 # required for progress indicator self.data_init = {'pos': '', 'rd_x': '', 'rd_y': '', 'latitude': '', 'longitude': '', 'geometry': ''} self.save_to_database = self.__save_standplaats @@ -279,13 +280,14 @@ def __update_status(self, final=False): def __parse_file(self, file_xml): data = self.data_init.copy() - tag_previous = None - tag_now = None + parent_tags = [] + for event, elem in ElementTree.iterparse(file_xml, events=("start", "end")): if event == 'start': - tag_previous = tag_now - tag_now = elem.tag + parent_tags.append(elem.tag) elif event == 'end': + parent_tags.pop() + # Note: elem.text is only guaranteed in 'end' event if elem.tag == self.object_tag_name: self.count_xml += 1 @@ -295,11 +297,14 @@ def __parse_file(self, file_xml): else: field_found = False - if self.db_tag_parent_fields and tag_previous: - parent_elem_tag = tag_previous + elem.tag + if self.db_tag_parent_fields and parent_tags: + parent_elem_tag = parent_tags[-1] + elem.tag field_parent_elem = self.db_tag_parent_fields.get(parent_elem_tag) if field_parent_elem: - data[field_parent_elem] = elem.text + if field_parent_elem in data and data[field_parent_elem]: + data[field_parent_elem] += "," + elem.text + else: + data[field_parent_elem] = elem.text field_found = True if not field_found: @@ -322,7 +327,7 @@ def __save_gemeente_woonplaats(self, data): self.__update_status() self.database.save_gemeente_woonplaats(data) - def __save_openbareruimte(self, data): + def __save_openbare_ruimte(self, data): if (self.__bag_einddatum_valid(data) and self.__bag_begindatum_valid(data) and data['status'] == "Naamgeving uitgegeven"): diff --git a/config.py b/config.py index a465b55..868379d 100644 --- a/config.py +++ b/config.py @@ -1,7 +1,7 @@ import locale -version = 56 -version_date = '20 June 2023' +version = 58 +version_date = '4 July 2023' locale.setlocale(locale.LC_ALL, 'nl_NL') @@ -23,7 +23,7 @@ # If an adressen table is created some BAG tables are no longer needed and can be deleted: # nummers, panden, verblijfsobjecten, ligplaatsen and standplaatsen. Set to False if you want to keep these tables. -# You can also delete these tables afterwards using the utils_sqlite_shrink.py script. +# You can also delete these tables afterward using the utils_sqlite_shrink.py script. delete_no_longer_needed_bag_tables = True # Public spaces with names longer than 24 characters also have a shortened name. Set to true to make short names the diff --git a/database_sqlite/database_sqlite.py b/database_sqlite/database_sqlite.py index ca3e62b..acc49e8 100644 --- a/database_sqlite/database_sqlite.py +++ b/database_sqlite/database_sqlite.py @@ -71,32 +71,40 @@ def save_openbare_ruimte(self, data): def save_nummer(self, data): # Note: Use replace, because BAG does not always contain unique id's self.connection.execute( - """REPLACE INTO nummers (id, postcode, huisnummer, huisletter, toevoeging, woonplaats_id, openbareruimte_id, - status) VALUES(?, ?, ?, ?, ?, ?, ?, ?); + """REPLACE INTO nummers (id, postcode, huisnummer, huisletter, toevoeging, woonplaats_id, + openbare_ruimte_id, status) VALUES(?, ?, ?, ?, ?, ?, ?, ?); """, (data["id"], data["postcode"], data["huisnummer"], data["huisletter"], data["toevoeging"], - data["woonplaats_id"], data["openbareruimte_id"], data["status"]) + data["woonplaats_id"], data["openbare_ruimte_id"], data["status"]) ) - def save_pand(self, data): # Note: Use replace, because BAG does not always contain unique id's self.connection.execute( """REPLACE INTO panden (id, bouwjaar, status, geometry) VALUES(?, ?, ?, ?) """, - (data["id"], data["bouwjaar"], data["status"], data["geometry"])) + (data["id"], data["bouwjaar"], data["status"], data["geometry"]) + ) def save_verblijfsobject(self, data): # Note: Use replace, because BAG does not always contain unique id's self.connection.execute( """REPLACE INTO verblijfsobjecten (id, nummer_id, pand_id, oppervlakte, rd_x, rd_y, latitude, longitude, - gebruiksdoel, status) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + gebruiksdoel, status) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?); """, (data["id"], data["nummer_id"], data["pand_id"], data["oppervlakte"], data["rd_x"], data["rd_y"], data["latitude"], data["longitude"], data["gebruiksdoel"], data["status"]) ) + if data["nevenadressen"]: + nevenadressen = data["nevenadressen"].split(",") + for neven_nummer_id in nevenadressen: + self.connection.execute(""" + INSERT INTO nevenadressen (verblijfsobject_id, neven_nummer_id, hoofd_nummer_id) VALUES (?, ?, ?); + """, (data["id"], neven_nummer_id, data["nummer_id"]) + ) + def save_ligplaats(self, data): # Note: Use replace, because BAG does not always contain unique id's self.connection.execute( @@ -130,27 +138,35 @@ def create_bag_tables(self): DROP TABLE IF EXISTS openbare_ruimten; CREATE TABLE openbare_ruimten (id INTEGER PRIMARY KEY, naam TEXT, lange_naam TEXT, verkorte_naam TEXT, - type TEXT, woonplaats_id INTEGER); + type TEXT, woonplaats_id INTEGER); DROP TABLE IF EXISTS nummers; CREATE TABLE nummers (id TEXT PRIMARY KEY, postcode TEXT, huisnummer INTEGER, huisletter TEXT, - toevoeging TEXT, woonplaats_id TEXT, openbareruimte_id TEXT, status TEXT); + toevoeging TEXT, woonplaats_id TEXT, openbare_ruimte_id TEXT, status TEXT); DROP TABLE IF EXISTS panden; CREATE TABLE panden (id TEXT PRIMARY KEY, bouwjaar INTEGER, status TEXT, geometry TEXT); DROP TABLE IF EXISTS verblijfsobjecten; CREATE TABLE verblijfsobjecten (id TEXT PRIMARY KEY, nummer_id TEXT, pand_id TEXT, - oppervlakte FLOAT, rd_x FLOAT, rd_y FLOAT, latitude FLOAT, longitude FLOAT, gebruiksdoel TEXT, - status TEXT); + oppervlakte FLOAT, rd_x FLOAT, rd_y FLOAT, latitude FLOAT, longitude FLOAT, gebruiksdoel TEXT, + status TEXT); + + DROP TABLE IF EXISTS nevenadressen; + CREATE TABLE nevenadressen ( + verblijfsobject_id TEXT, + neven_nummer_id TEXT, + hoofd_nummer_id TEXT, + PRIMARY KEY (verblijfsobject_id, neven_nummer_id) + ); DROP TABLE IF EXISTS ligplaatsen; CREATE TABLE ligplaatsen (id TEXT PRIMARY KEY, nummer_id TEXT, rd_x FLOAT, rd_y FLOAT, latitude FLOAT, - longitude FLOAT, status TEXT, geometry TEXT); + longitude FLOAT, status TEXT, geometry TEXT); DROP TABLE IF EXISTS standplaatsen; CREATE TABLE standplaatsen (id TEXT PRIMARY KEY, nummer_id TEXT, rd_x FLOAT, rd_y FLOAT, latitude FLOAT, - longitude FLOAT, status TEXT, geometry TEXT); + longitude FLOAT, status TEXT, geometry TEXT); """) self.connection.commit() @@ -173,48 +189,60 @@ def create_indices_adressen(self): def create_adressen_from_bag(self): + utils.print_log('create adressen tabel: import adressen') self.connection.executescript(f""" DROP TABLE IF EXISTS adressen; CREATE TABLE adressen (nummer_id TEXT PRIMARY KEY, pand_id TEXT, verblijfsobject_id TEXT, gemeente_id INTEGER, woonplaats_id INTEGER, openbare_ruimte_id INTEGER, object_type TEXT, gebruiksdoel TEXT, postcode TEXT, huisnummer INTEGER, huisletter TEXT, toevoeging TEXT, - oppervlakte FLOAT, rd_x FLOAT, rd_y FLOAT, latitude FLOAT, longitude FLOAT, bouwjaar INTEGER, + oppervlakte FLOAT, rd_x FLOAT, rd_y FLOAT, latitude FLOAT, longitude FLOAT, bouwjaar INTEGER, + hoofd_nummer_id TEXT, geometry TEXT); INSERT INTO adressen (nummer_id, pand_id, verblijfsobject_id, gemeente_id, woonplaats_id, openbare_ruimte_id, object_type, gebruiksdoel, postcode, huisnummer, huisletter, toevoeging, oppervlakte, rd_x, rd_y, longitude, latitude, bouwjaar, geometry) SELECT - n.id AS nummer_id, - p.id AS pand_id, - v.id AS verblijfsobject_id, - w.gemeente_id, - o.woonplaats_id, - o.id, - 'verblijfsobject', - v.gebruiksdoel, - n.postcode, - n.huisnummer, - n.huisletter, - n.toevoeging, - v.oppervlakte, - v.rd_x, - v.rd_y, - v.longitude, - v.latitude, - p.bouwjaar, - p.geometry + n.id AS nummer_id, + p.id AS pand_id, + v.id AS verblijfsobject_id, + w.gemeente_id, + o.woonplaats_id, + o.id, + 'verblijfsobject', + v.gebruiksdoel, + n.postcode, + n.huisnummer, + n.huisletter, + n.toevoeging, + v.oppervlakte, + v.rd_x, + v.rd_y, + v.longitude, + v.latitude, + p.bouwjaar, + p.geometry FROM nummers n - LEFT JOIN openbare_ruimten o ON o.id = n.openbareruimte_id + LEFT JOIN openbare_ruimten o ON o.id = n.openbare_ruimte_id LEFT JOIN woonplaatsen w ON w.id = o.woonplaats_id LEFT JOIN verblijfsobjecten v ON v.nummer_id = n.id LEFT JOIN panden p ON v.pand_id = p.id; """) + utils.print_log('create adressen tabel: import ligplaatsen data') self.adressen_import_ligplaatsen() + + utils.print_log('create adressen tabel: import standplaatsen data') self.adressen_import_standplaatsen() + + utils.print_log('create adressen tabel: import woonplaatsen from nummers') self.adressen_update_woonplaatsen_from_nummers() + + utils.print_log('create adressen tabel: update nevenadressen data') + self.adressen_update_nevenadressen() + + utils.print_log('create adressen tabel: create indices') self.create_indices_adressen() self.connection.commit() @@ -245,7 +273,41 @@ def adressen_import_standplaatsen(self): WHERE s.nummer_id = adressen.nummer_id; """) - # woonplaats_id in nummers overruled woonplaats_id van de openbare ruimte. + def adressen_update_nevenadressen(self): + self.connection.executescript(""" + UPDATE adressen SET + hoofd_nummer_id = n.hoofd_nummer_id, + pand_id = n.pand_id, + verblijfsobject_id = n.verblijfsobject_id, + gebruiksdoel = n.gebruiksdoel, + oppervlakte = n.oppervlakte, + rd_x = n.rd_x, + rd_y = n.rd_y, + latitude = n.latitude, + longitude = n.longitude, + bouwjaar = n.bouwjaar, + geometry = n.geometry + FROM ( + SELECT + nevenadressen.hoofd_nummer_id, + nevenadressen.neven_nummer_id, + adressen.pand_id, + adressen.verblijfsobject_id, + adressen.gebruiksdoel, + adressen.oppervlakte, + adressen.rd_x, + adressen.rd_y, + adressen.latitude, + adressen.longitude, + adressen.bouwjaar, + adressen.geometry + FROM nevenadressen + LEFT JOIN adressen ON nevenadressen.hoofd_nummer_id = adressen.nummer_id + ) AS n + WHERE n.neven_nummer_id = adressen.nummer_id; + """) + + # woonplaats_id in nummers overrule woonplaats_id van de openbare ruimte. def adressen_update_woonplaatsen_from_nummers(self): self.connection.executescript(""" UPDATE adressen SET @@ -258,6 +320,7 @@ def delete_no_longer_needed_bag_tables(self): self.connection.executescript(""" DROP TABLE IF EXISTS nummers; DROP TABLE IF EXISTS panden; + DROP TABLE IF EXISTS nevenadressen; DROP TABLE IF EXISTS verblijfsobjecten; DROP TABLE IF EXISTS ligplaatsen; DROP TABLE IF EXISTS standplaatsen; @@ -344,7 +407,7 @@ def test_bag_adressen(self): # Het is makkelijk om per ongeluk een gemeenten.csv te genereren die niet in UTF-8 is. Testen dus. naam = self.fetchone("SELECT naam FROM gemeenten WHERE id=1900") - utils.print_log("test: Gemeentenamen moeten in UTF-8 zijn: " + naam, naam != 'Súdwest-Fryslân') + utils.print_log("test: gemeentenamen moeten in UTF-8 zijn: " + naam, naam != 'Súdwest-Fryslân') aantal = self.fetchone("SELECT COUNT(*) FROM adressen WHERE adressen.latitude IS NULL AND pand_id IS NOT NULL;") utils.print_log("test: panden zonder locatie: " + str(aantal), aantal > 0) diff --git a/import_bag.py b/import_bag.py index 19e1eb8..4428fb8 100644 --- a/import_bag.py +++ b/import_bag.py @@ -47,7 +47,6 @@ db_sqlite.create_indices_bag() if config. create_adressen_table: - utils.print_log('create adressen tabel') db_sqlite.create_adressen_from_bag() db_sqlite.adressen_fix_bag_errors() db_sqlite.test_bag_adressen() diff --git a/readme.MD b/readme.MD index d988752..5157a46 100644 --- a/readme.MD +++ b/readme.MD @@ -14,7 +14,7 @@ Additional scripts will convert this SQLite database to other formats, like CSV. If you don't want to run the script yourself, download the latest BAG in SQLite or CSV format from our [releases](https://github.com/digitaldutch/BAG_parser/releases) section. -## About the BAG ## +## About the BAG The Dutch public addresses and buildings database (BAG or Basisregistratie Adressen en Gebouwen) is freely downloadable from the [Dutch cadastre](https://www.kadaster.nl/-/kosteloze-download-bag-2.0-extract) agency named Kadaster. Hooray 🙂. @@ -24,7 +24,7 @@ It also does not include municipalities or provinces and provides coordinates us expect named [Rijksdriehoekscoördinaten](https://nl.wikipedia.org/wiki/Rijksdriehoeksco%C3%B6rdinaten)😲. -## What this parser does ## +## What this parser does This Python utility parses the BAG database and converts it into a clean, easy to read & use SQLite database. Municipalities (gemeenten) and provinces (provincies) are added. Rijksdriehoekscoördinaten coordinates are converted to standard WGS84 latitude and longitude coordinates. A few BAG bugs are fixed. @@ -33,19 +33,21 @@ Several tables (nummers, verblijfsobjecten, panden, ligplaatsen and standplaatse table. The SQLite database can be used directly, or as a source to generate a *.csv file or update your own addresses databases. There are a couple of options available in the [config.py](config.py). -## Requirements ## -* Python 3.10 or 3.11. Python 3.8 probably works, but is 20% slower. +## Requirements +* Python 3.11 works. Older Python versions are slower and may work, but are not tested. -## Usage ## +## Usage * Download, or better (because it allows easy updates) use git to download the BAG parser. Install parser: `git clone https://github.com/digitaldutch/BAG_parser` Update parser: `git pull https://github.com/digitaldutch/BAG_parser` -* [Download the BAG (2.8 GB)](https://service.pdok.nl/kadaster/adressen/atom/v1_0/downloads/lvbag-extract-nl.zip) and save the file as `bag.zip` in the `input` folder. +* Download the BAG (3 GB) from [kadaster.nl](https://www.kadaster.nl/-/kosteloze-download-bag-2-0-extract) + or directly from [pdok.nl](https://service.pdok.nl/kadaster/adressen/atom/v1_0/downloads/lvbag-extract-nl.zip) + and save the file as `bag.zip` in the `input` folder. * The [gemeenten.csv](input/gemeenten.csv) file is already included in the `input` folder, but you can [download the latest version from the CBS website](https://www.cbs.nl/nl-nl/onze-diensten/methoden/classificaties/overig/gemeentelijke-indelingen-per-jaar). Save it as `gemeenten.csv` in the input folder. * Set your options in [config.py](config.py) * Run `import_bag.py` -* Drink cocktails for 40 minutes 🌴🍹😎 while watching the progress bar. +* Drink cocktails for 35 minutes 🌴🍹😎 while watching the progress bar. * Open the SQLite database with your favorite adminstration tool. I like [DBeaver](https://dbeaver.io/). Here's an example query on SQLite database to get information about postcode 2514GL, huisnummer 78 (Paleis Noordeinde): ```SQL @@ -63,7 +65,8 @@ SELECT a.rd_x, a.rd_y, a.oppervlakte AS vloeroppervlakte, - a.gebruiksdoel + a.gebruiksdoel, + a.hoofd_nummer_id FROM adressen a LEFT JOIN openbare_ruimten o ON a.openbare_ruimte_id = o.id LEFT JOIN gemeenten g ON a.gemeente_id = g.id @@ -78,14 +81,14 @@ SQLite data into a live Firebird database. ## Python commands ## -### [import_bag.py](import_bag.py) ### -Parses the original BAG file and transforms it into a SQLite database. Takes about 50 minutes to complete, or double +### [import_bag.py](import_bag.py) +Parses the original BAG file and transforms it into a SQLite database. Takes about 35 minutes to complete, or double that if you switch on the `parse_geometries` option in the [config.py](config.py). -### [export_to_csv_postcodes.py](export_to_csv_postcodes.py) ### +### [export_to_csv_postcodes.py](export_to_csv_postcodes.py) Exports the addresses in SQLite database to a *.csv file with address info only. Takes about 15 seconds. -### [export_to_csv.py](export_to_csv.py) ### +### [export_to_csv.py](export_to_csv.py) Exports the addresses in SQLite database to a *.csv file including additional information, like year of construction, latitude, longitude, floor area and intended use of buildings. Takes about 40 seconds. @@ -93,23 +96,32 @@ latitude, longitude, floor area and intended use of buildings. Takes about 40 se Exports statistics of postal code groups (4, 5 or all 6 characters) in SQLite database to a *.csv file including number of addresses and average lat/lon of addresses in that zone. Takes several seconds. -### [test_sqlite_db.py](test_sqlite_db.py) ### +### [test_sqlite_db.py](test_sqlite_db.py) Checks de SQLite database for info and errors. `import_bag.py` also performs these tests after parsing. -### [utils_sqlite_shrink.py](utils_sqlite_shrink.py) ### +### [utils_sqlite_shrink.py](utils_sqlite_shrink.py) Reduces the SQlite database size by removing BAG tables (nummers, verblijfsobjecten, panden, ligplaatsen and standplaatsen) that are no longer needed due to the new 'adressen' table. The parser also does this as a final step if `delete_no_longer_needed_bag_tables` is set to True in [config.py](config.py). -## Limitations ## +## Adressen table +An adres is a nevenadres if the `hoofd_nummer_id` field is set. It points to the `nummer_id` of the hoofdadres. + +## Limitations * Only active addresses are included. History data is left out. -* Residence info (verblijfsobjecten) is only added to the main address (hoofdadres). Other addresses (nevenadres) are ignored. * Probably several more things that I forgot. Let me know by filing a GitHub issue. * The WGS84 coordinates are calculated using [approximation equations by F.H. Schreutelkamp and G.L. Strang van Hees](docs/Benaderingsformules_RD_WGS.pdf). This conversion has an error of a few decimeters. Don't use the WGS84 coordinates if you need higher accuracy. +## Other tools + +#### nlextract Just a limited amount of data is parsed. If you need more data or professional support, buy it from [nlextract](https://nlextract.nl/), -who have their own, much more complete [parser](https://github.com/nlextract/NLExtract). +who have their own, much more complex but also complete [parser](https://github.com/nlextract/NLExtract). + +#### bagconv +Bert hubert has [written a parser](https://berthub.eu/articles/posts/dutch-postcode-and-building-database/) in C++, +[bagconv](https://github.com/berthubert/bagconv), which is quite similar to this one. -## License ## +## License This software is made available under the [MIT license](LICENSE). \ No newline at end of file