How to Create a Simple WebGIS with FastAPI, PostGIS, and Leaflet.js

How to Create a Simple WebGIS with FastAPI, PostGIS, and Leaflet.js

Introduction

In this tutorial, we’ll walk through creating a WebGIS application using FastAPIPostGIS, and Leaflet.js. The goal is to create a map that displays points stored in a PostGIS database and allows users to add new points by clicking on the map. We’ll cover setting up the FastAPI backend, creating the PostGIS database, and developing the frontend with Leaflet.js to display the map.

Prerequisites

Before we begin, you’ll need the following:

  • Python (version 3.7 or higher)
  • PostgreSQL with PostGIS extension installed
  • FastAPI and Uvicorn for the backend
  • Leaflet.js for the frontend map

Ensure you have PostgreSQL and PostGIS set up and running on your machine.

Installing Required Libraries

First, install the necessary Python libraries:

pip install fastapi uvicorn asyncpg sqlalchemy alembic pydantic psycopg2

For Leaflet.js, we’ll use a CDN, so no installation is needed on the Python side.


Step 1: Set Up PostgreSQL and PostGIS

Create a PostgreSQL database and enable PostGIS:

# Create a new database
CREATE DATABASE webgis;

# Enable PostGIS extension
\c webgis
CREATE EXTENSION postgis;

Next, create a points table to store our geographic data (point name, latitude, and longitude):

CREATE TABLE points (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255),
    latitude DOUBLE PRECISION,
    longitude DOUBLE PRECISION,
    geom GEOGRAPHY(Point, 4326)
);

Step 2: Set Up FastAPI Backend

We’ll create a FastAPI app to interact with the PostGIS database. The app will expose endpoints to add new points and fetch all existing points.

Create main.py for FastAPI Backend

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from sqlalchemy import create_engine, Column, Integer, String, Float
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy.future import select
from sqlalchemy.orm import Session

# Set up database
DATABASE_URL = "postgresql://username:password@localhost/webgis"
engine = create_engine(DATABASE_URL, echo=True)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()

# Create a model for points
class Point(Base):
    __tablename__ = 'points'
    id = Column(Integer, primary_key=True, index=True)
    name = Column(String, index=True)
    latitude = Column(Float)
    longitude = Column(Float)
    geom = Column(String)  # GeoJSON representation of point

Base.metadata.create_all(bind=engine)

# FastAPI app setup
app = FastAPI()

# Define Pydantic model for the point data
class PointCreate(BaseModel):
    name: str
    latitude: float
    longitude: float

@app.post("/add_point/")
async def add_point(point: PointCreate, db: Session = Depends(get_db)):
    db_point = Point(name=point.name, latitude=point.latitude, longitude=point.longitude)
    db.add(db_point)
    db.commit()
    db.refresh(db_point)
    return {"message": "Point added successfully!", "id": db_point.id}

@app.get("/get_points/")
async def get_points(db: Session = Depends(get_db)):
    points = db.execute(select(Point)).scalars().all()
    return points

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

This FastAPI app does two things:

  1. It exposes an endpoint to add a point (POST /add_point/).
  2. It exposes an endpoint to get all points from the database (GET /get_points/).

Step 3: Develop the Frontend with Leaflet.js

Now, let’s create the frontend using Leaflet.js to display the map and interact with the backend.

Create index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Simple WebGIS</title>
    <link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" />
    <script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
    <style>
        #map { height: 500px; width: 100%; }
        #searchBox { width: 200px; margin: 20px; padding: 5px; }
    </style>
</head>
<body>
    <input type="text" id="searchBox" placeholder="Enter point name..." />
    <button onclick="searchPoint()">Search</button>
    <div id="map"></div>

    <script>
        let map = L.map('map').setView([37.5, 45.0], 10);  // Default view
        L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);

        let markers = {};  // Store markers by name

        async function loadPoints() {
            try {
                const response = await fetch("http://127.0.0.1:8000/get_points/");
                const points = await response.json();

                points.forEach(point => {
                    let marker = L.marker([point.latitude, point.longitude])
                        .addTo(map)
                        .bindPopup(`<b>${point.name}</b>`);
                    markers[point.name.toLowerCase()] = marker;
                });

            } catch (error) {
                console.error("Error loading points:", error);
            }
        }

        loadPoints();

        function searchPoint() {
            let searchQuery = document.getElementById("searchBox").value.toLowerCase();
            if (markers[searchQuery]) {
                let marker = markers[searchQuery];
                map.setView(marker.getLatLng(), 15);  // Zoom to the point
                marker.openPopup();
            } else {
                alert("Point not found!");
            }
        }
    </script>
</body>
</html>

In this HTML file:

  1. We initialize the Leaflet map with a default view.
  2. We fetch points from the FastAPI backend using fetch and display them on the map.
  3. The search box allows users to search for points by name and zoom to them.

Step 4: Run the Application

Start FastAPI Server

Run the FastAPI server using Uvicorn:

uvicorn main:app --reload

Open index.html

Open the index.html in your browser. The map should load, and you should be able to add points and zoom to them using the search box.


Conclusion

In this tutorial, we created a simple WebGIS application using FastAPI, PostGIS, and Leaflet.js. We set up a PostgreSQL database with PostGIS, created a FastAPI backend to interact with the database, and built a frontend with Leaflet.js to display the map and add points. This simple WebGIS can be extended with more features such as editing points, adding more layers, and geospatial analysis.