├── .gitignore ├── LICENSE ├── README.md ├── coordinates_to_address.py └── convert.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pbf 2 | *.db 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 punnerud 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RGCosm - Reverse Geocode for OpenStreetmap 2 | 3 | Locally hosted OpenStreetmap using sqlite3 for reverse geocode. 4 | So you easily can find adresses based on coordinates. 5 | 6 | Download the pbf file from: 7 | https://download.geofabrik.de/ 8 | 9 | Then use convert.py to create the database: 10 | ``` 11 | python3 convert.py 12 | ``` 13 | 14 | You have to change "norway-latest.osm.pbf" in convert.py into your filename. 15 | The "norway-latest.osm.pbf" is about 1GB and the sqlite3 end up 10GB. With indexes 16GB. So don't try with the biggest areas for starting. Takes about 15minutes for the norway file. 16 | 17 | To speed up your queries it is highly recommended to add indexes. This increase the size around 50% and takes a couple of minutes to create: 18 | ``` 19 | CREATE INDEX "nodes index lat" ON "nodes" ( "lat" ); 20 | CREATE INDEX "nodes index lon" ON "nodes" ( "lon" ); 21 | ``` 22 | 23 | Adding indexes change the search time for my Norway file from 10 to 0.15 seconds. Changing the lookaround query can also reduce the search time, at the risk that you miss an adress if the closest adress is more far away. 24 | 25 | 26 | Mac users, I found this to work for installation of osmium for Python: 27 | ``` 28 | brew install cmake 29 | brew install wheel 30 | brew install osmium-tool 31 | python3 -m pip install osmium 32 | ``` 33 | 34 | Premade sqlite3 database for Norway with indexes if you just want to try it: 35 | https://www.dropbox.com/s/4rrhxpfzulqbvxr/osm.db?dl=0 36 | -------------------------------------------------------------------------------- /coordinates_to_address.py: -------------------------------------------------------------------------------- 1 | def coordinates_to_address(lat, lon): 2 | # Connect to the database 3 | conn = sqlite3.connect('db.db') 4 | cursor = conn.cursor() 5 | 6 | # Retrieve addresses within a +/- 0.001 degree range of the original coordinates 7 | cursor.execute(''' 8 | SELECT id, lat, lon, tags 9 | FROM nodes 10 | WHERE lat >= ? AND lat <= ? AND lon >= ? AND lon <= ? 11 | ''', (lat - 0.001, lat + 0.001, lon - 0.001, lon + 0.001)) 12 | rows = cursor.fetchall() 13 | if len(rows) == 0: 14 | conn.close() # Close the connection before returning 15 | return None 16 | 17 | # Find the address with the smallest distance from the original coordinates 18 | min_distance = float('inf') 19 | closest_address = None 20 | for row in rows: 21 | id, node_lat, node_lon, tags = row 22 | distance = math.sqrt((node_lat - lat) ** 2 + (node_lon - lon) ** 2) 23 | if distance < min_distance: 24 | if tags.count('addr:') > 2: 25 | min_distance = distance 26 | closest_address = {'id': id, 'lat': node_lat, 'lon': node_lon, 'tags': tags} 27 | 28 | # Parse the tags column to find the address 29 | #address = {} 30 | #for tag in closest_address['tags'].split(','): 31 | # k, v = tag.split(':', 1) 32 | # address[k] = v 33 | 34 | # Close the connection to the database 35 | conn.close() 36 | 37 | # Return the address 38 | return closest_address 39 | -------------------------------------------------------------------------------- /convert.py: -------------------------------------------------------------------------------- 1 | import os 2 | import osmium 3 | import sqlite3 4 | import time 5 | import subprocess 6 | 7 | class OsmHandler(osmium.SimpleHandler): 8 | def __init__(self, total_nodes, total_ways): 9 | osmium.SimpleHandler.__init__(self) 10 | self.conn = sqlite3.connect('osm.db') 11 | self.cursor = self.conn.cursor() 12 | self.cursor.execute('PRAGMA synchronous = OFF') 13 | self.cursor.execute('PRAGMA journal_mode = MEMORY') 14 | self.cursor.execute('''CREATE TABLE IF NOT EXISTS nodes (id INTEGER PRIMARY KEY, lat REAL, lon REAL, tags TEXT)''') 15 | self.cursor.execute('''CREATE TABLE IF NOT EXISTS ways (id INTEGER PRIMARY KEY, nodes TEXT, tags TEXT)''') 16 | self.conn.commit() 17 | 18 | self.node_count = 0 19 | self.way_count = 0 20 | self.total_nodes = total_nodes 21 | self.total_ways = total_ways 22 | self.total_elements = total_nodes + total_ways 23 | self.last_update = time.time() 24 | self.batch_size = 10000 25 | 26 | def node(self, n): 27 | self.cursor.execute('''INSERT INTO nodes (id, lat, lon, tags) VALUES (?, ?, ?, ?)''', 28 | (n.id, n.location.lat, n.location.lon, str(dict(n.tags)))) 29 | self.node_count += 1 30 | self._check_commit() 31 | 32 | def way(self, w): 33 | self.cursor.execute('''INSERT INTO ways (id, nodes, tags) VALUES (?, ?, ?)''', 34 | (w.id, ' '.join(map(str, w.nodes)), str(dict(w.tags)))) 35 | self.way_count += 1 36 | self._check_commit() 37 | 38 | def _check_commit(self): 39 | if (self.node_count + self.way_count) % self.batch_size == 0: 40 | self.conn.commit() 41 | self._update_progress() 42 | 43 | def _update_progress(self): 44 | current_time = time.time() 45 | if current_time - self.last_update >= 20: 46 | total_processed = self.node_count + self.way_count 47 | progress_percentage = (total_processed / self.total_elements) * 100 48 | print(f"Processed {self.node_count} nodes and {self.way_count} ways ({progress_percentage:.2f}%)") 49 | self.last_update = current_time 50 | 51 | def get_total_elements(filename): 52 | result = subprocess.run(['osmium', 'fileinfo', '-e', filename], capture_output=True, text=True) 53 | lines = result.stdout.split('\n') 54 | nodes = 0 55 | ways = 0 56 | for line in lines: 57 | if 'Number of nodes:' in line: 58 | nodes = int(line.split(':')[1].strip()) 59 | elif 'Number of ways:' in line: 60 | ways = int(line.split(':')[1].strip()) 61 | if nodes == 0 or ways == 0: 62 | raise ValueError(f"Could not parse node and way counts from osmium output: {result.stdout}") 63 | return nodes, ways 64 | 65 | # Get total number of nodes and ways 66 | print("Analyzing file...") 67 | total_nodes, total_ways = get_total_elements('norway-latest.osm.pbf') 68 | print(f"File contains {total_nodes} nodes and {total_ways} ways") 69 | 70 | # Load the OpenStreetMap file in pbf format 71 | handler = OsmHandler(total_nodes, total_ways) 72 | print("Starting conversion...") 73 | handler.apply_file('norway-latest.osm.pbf') 74 | 75 | # Save final changes to the database 76 | handler.conn.commit() 77 | handler.conn.close() 78 | print(f"Conversion complete. Processed {handler.node_count} nodes and {handler.way_count} ways") 79 | --------------------------------------------------------------------------------