This tutorial will guide you through the process of building a custom geocoding API utilizing Python and PostGIS, enabling address searches on an OpenLayers map. In addition to implementing autocomplete functionality for a more user-friendly experience, we will also demonstrate how to zoom in to the selected address upon click.
We will go through following steps for implementing this functionality
- Install Required Libraries
- Set up PostgreSQL Database with PostGIS Extension
- Load Geospatial points Data into Your Database
- Create a Flask Application and define a Geocoding Function
- Add Search Box and Autocomplete Feature to Web Page, Handle User Input and Send Requests to Flask Application and Display Search Results on Map Using OpenLayers
You can find the complete code and point shapefile at this github repo link
1. Install Required Libraries
To start with, you will need to install the following Python libraries:
- Flask: A lightweight web framework for building web applications.
- Flask-Cors: A Flask extension for handling Cross-Origin Resource Sharing (CORS), which is necessary for allowing your geocoding service to be accessed by other domains.
- psycopg2: A PostgreSQL adapter for Python.
- sqlalchemy: A SQL toolkit and ORM for Python.
1 2 3 |
(base) geoknight@pop-os:~$conda create -n spatial-dev.guru python=3.10 (base) geoknight@pop-os:~$conda activate spatial-dev.guru (spatial-dev.guru) geoknight@pop-os:~$conda install -c conda-forge Flask Flask-Cors psycopg2 sqlalchemy |
2. Set up PostgreSQL Database with PostGIS Extension
Next, you will need to set up a PostgreSQL database with the PostGIS extension installed. PostGIS is a spatial database extension for PostgreSQL that provides support for geographic objects, allowing you to store and query spatial data.
You can follow the instructions on the PostGIS website to download and install PostGIS: https://postgis.net/install/
Once you have installed PostGIS, you can create a new PostgreSQL database with the following command:
1 2 |
createdb mygeodb |
Then, you can enable the PostGIS extension in this database with the following command:
1 2 3 4 5 6 |
psql mygeodb -c "CREATE EXTENSION postgis;" psql mygeodb -c "CREATE EXTENSION</code> <code>postgis</code>_raster;" psql mygeodb -c "CREATE EXTENSION</code> <code>postgis</code>_sfcgal;" psql mygeodb -c "CREATE EXTENSION</code> <code>fuzzystrmatch</code>;" psql mygeodb -c "CREATE EXTENSION</code> <code>postgis</code>_tiger_geocoder;" psql mygeodb -c "CREATE EXTENSION</code> <code>postgis</code>_topology;" |
3. Load Geospatial Data into Your Database
Once you have set up your database with PostGIS, you can load geospatial data into it. You can use a variety of tools to load data, such as ogr2ogr, shp2pgsql, or QGIS or python. You can follow below tutorials to import the shapefile.
You can use sample points shapefile that I have used in this tutorial from below link at github repo.
4. Create a Flask Application and define a Geocoding Function
The following code is a Python Flask application that sets up an API endpoint for an autocomplete functionality for geocoding searches. The endpoint queries a PostgreSQL database with PostGIS extension for the top 10 matching entries based on a search term parameter provided in the request. The response includes the matching location’s identifier, longitude, and latitude, and is then formatted as a JSON object and returned to the client. The application utilizes Flask-CORS to allow cross-origin requests.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
from flask import Flask, jsonify, request from flask_cors import CORS import psycopg2 app = Flask(__name__) CORS(app) # Set up the connection parameters host = 'localhost' port = "5432" database = 'postgres' user = 'postgres' password = 'admin' # Connect to the database conn = psycopg2.connect(host=host, database=database, user=user, password=password, port = port) # Open a cursor to perform database operations cur = conn.cursor() # Endpoint for autocomplete functionality @app.route('/autocomplete', methods=['GET']) def autocomplete(): table_name = 'sample_pois' # Get the search term from the request search_term = request.args.get('search_term', '') # Execute a query to get the top 10 matching entries cur.execute(F"SELECT identifier, st_x(geometry) lon, st_y(geometry) lat FROM {table_name} where identifier ILIKE '%{search_term}%' LIMIT 10") # Fetch the results rows = cur.fetchall() # Convert the results to a JSON response results = [] for row in rows: result = {'value': row[0], 'lon': row[1], 'lat': row[2]} # replace 'id' and 'value' with your column names results.append(result) response = {'results': results} return jsonify(response) if __name__ == '__main__': app.run(debug=True) |
5. Add Search Box and Autocomplete Feature to Web Page, Handle User Input and Send Requests to Flask Application and Display Search Results on Map Using OpenLayers
The given code is a HTML web page that includes a search box with autocomplete functionality to search for places. The web page uses OpenLayers to display a map and the user can select a search result to display it on the map.
The page sends a GET request to a Flask application when the user types in the search box, and Flask responds with matching search results from a PostgreSQL database. The search results are displayed in a dropdown list below the search box.
When the user selects a search result from the dropdown list, the web page centers the map on the selected location and displays the latitude and longitude of the selected location in a popup using OpenLayers’ overlay functionality. The popup is updated with the latitude and longitude every time the user selects a new location.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
<html> <head> <title>GeoCoding</title> <!-- Include OpenLayers library --> <script src="dependencies/ol.js"></script> <link rel="stylesheet" href="dependencies/ol.css"> <link rel="stylesheet" href="dependencies/style.css"> </head> <body> <!-- Container for search box --> <div id = "search-container" style="position: absolute;z-index: 9999;background: white;padding: 10px;right: 0;"> </div> <!-- Map container --> <div id = "map"> <!-- Popup container --> <div id = "popup"></div> </div> </body> <script> //Define Map var map = new ol.Map({ target: 'map', layers: [ new ol.layer.Tile({ source: new ol.source.OSM() }) ], view: new ol.View({ center: ol.proj.fromLonLat([13.068078763917814, 52.94515204359534]), zoom: 9 }) }); // Define overlay popup for dislayong lat lon var element = document.getElementById("popup") var overlay = new ol.Overlay({ element: element, offset: [0, -15], positioning: 'bottom-center', className: 'ol-tooltip-measure ol-tooltip .ol-tooltip-static' }); overlay.setPosition([0,0]); overlay.element.style.display = 'block'; map.addOverlay(overlay); // Create the search textbox and autocomplete functionality var searchBox = document.createElement('input'); searchBox.type = 'text'; searchBox.placeholder = 'Search...'; document.getElementById('search-container').appendChild(searchBox); var searchResults = document.createElement('ul'); searchResults.style.listStyle = 'none' document.getElementById('search-container').appendChild(searchResults); searchBox.addEventListener('input', function() { // Clear the previous search results searchResults.innerHTML = ''; // Get the search term from the input box var search_term = searchBox.value; // If the search term is at least one character long, perform the autocomplete search if (search_term.length >= 1) { var xhr = new XMLHttpRequest(); // Send a GET request to the autocomplete endpoint, passing the search term as a query parameter xhr.open('GET', 'http://127.0.0.1:5000/autocomplete?search_term=' + encodeURIComponent(search_term)); xhr.onload = function() { if (xhr.status === 200) { var response = JSON.parse(xhr.responseText); response.results.forEach(function(result) { // For each search result, create a list item and append it to the search results container var li = document.createElement('li'); li.textContent = result.value; li.addEventListener('click', function() { // When a search result is clicked, populate the search box with the result value, clear the search results container, and center the map on the result location searchBox.value = result.value; searchResults.innerHTML = ''; var lonLat = [result.lon, result.lat]; map.getView().animate({ center: ol.proj.fromLonLat(lonLat), zoom: 15 }); // Update the popup with the lat and lon of the result location, and position it over the location overlay.element.innerHTML = 'Lat: ' + result.lat + '<br/> Lon: ' + result.lon; // Set popup position to lat lon overlay.setPosition(ol.proj.fromLonLat(lonLat)); }); searchResults.appendChild(li); }); } }; xhr.send(); } }); </script> <style> ul { list-style-type: none; padding: 0; margin: 0; cursor: pointer; } li:nth-child(even) { background-color: #f2f2f2; /* even items have a light gray background */ } li:nth-child(odd) { background-color: #ffffff; /* odd items have a white background */ } li:hover { background-color: #c2c2c2; /* hover effect */ } </style> </html> |
In conclusion, the tutorial successfully demonstrated how to build a custom geocoding service with autocomplete using Python, PostGIS, and OpenLayers for address lookup. By following the steps outlined in the tutorial, one can create their own geocoding service and use it to perform address lookups in their web map applications. The tutorial covered the necessary steps from setting up the backend database to building the frontend interface using OpenLayers. Overall, this tutorial provides a comprehensive guide for anyone looking to build a custom geocoding service and add address lookup functionality to their web map application.
I hope this tutorial will create a good foundation for you. If you want tutorials on another GIS topic or you have any queries, please send an email at contact@spatial-dev.guru.