TALON (Topographic Analysis & Land Ownership Navigator)

Role: Solo, end-to-end. architecture, backend, frontend, data engineering, ML pipelines, infrastructure, product
Tech Stack: Python (FastAPI), PostgreSQL (PostGIS), Astro/Preact, MapLibre GL, Three.js/Potree, OpenAI API, Docker
Live Site: Coming soon. Staging is up on self-hosted infrastructure; open beta once the go-live list is worked through

Overview

TALON is a land acquisition intelligence system that combines geospatial analysis, machine learning, and conversational AI to evaluate real estate parcels for development, investment, and conservation.

Consumer maps show you where things are. TALON calculates what’s possible. It ingests raw government datasets (tax parcels, zoning vectors, LiDAR elevation models, soil surveys, satellite imagery, forestry data) and enriches every parcel with dozens of derived metrics: slope exposure by compass direction, canopy density, timber volume, soil suitability, and more. You can explore the data through natural-language conversation (“Find me a 10-acre lot with southern exposure, mature hardwood, and road access”) or through preset filters tuned for specific use cases, solar development, conservation, timber investment, and so on.

Coverage is currently focused on 14 western North Carolina counties at 1m LiDAR resolution, ahead of an open beta. Earlier iterations spread across North Carolina, Massachusetts, Vermont, and several Virginia counties; I deliberately descoped to guarantee consistent depth on every county I ship rather than inconsistent breadth across states.

System Architecture

TALON uses hexagonal architecture (Ports & Adapters) with strict dependency inversion. The domain layer is pure business logic with zero framework imports, surrounded by application services, abstract port interfaces, and concrete adapters for the database, APIs, and web layer.

graph TD
    subgraph "Data Pipeline (Offline)"
    A[Raw Data: GeoPackages, LiDAR, Soil, Satellite] --> B{Python ETL Engine}
    B -->|GeoPandas/Shapely| C[Clean & Reproject to EPSG:4326]
    B -->|Rasterio/laspy| D[Slope, Elevation & Forestry Analysis]
    B -->|Multiprocessing| E[Spatial Joins & Enrichment]
    E --> F[(PostgreSQL + PostGIS)]
    end

    subgraph "Intelligence Layer"
    F --> G[FastAPI Async Backend]
    G --> H[LLM Conversational Engine]
    G --> I[ML Owner Classifier]
    G --> J[LiDAR Source Orchestrator]
    end

    subgraph "Application Layer"
    G --> K[Astro SSR + Preact Islands]
    K --> L[MapLibre GL Visualization]
    K --> M[Three.js / Potree 3D Viewer]
    end

Components

How It Works

Geospatial databases are powerful but demand specialized query knowledge. A user shouldn’t need to know PostGIS to find land. So I built a multi-turn conversational search powered by LLM function calling.

2. Multi-Source LiDAR Orchestration

LiDAR data comes from multiple government sources with different formats, quality levels, and coverage. Users shouldn’t have to know which source covers their area, or even that there are multiple sources. So I built a 3-tier discovery and extraction system with smart fallback.

discover_and_reconcile() queries all tiers in parallel, ranks results by quality level and recency, and presents unified options. Deterministic caching (keyed by tier + survey date + name) prevents redundant downloads, and a stale-job watchdog sweeps stuck processing tasks. Extracted point clouds feed into a Potree-based 3D viewer with TPI (Topographic Position Index) and HAG (Height Above Ground) channel overlays.

3. High-Compute Spatial Processing

Calculating the average slope of a 100-acre parcel from raw LiDAR is computationally expensive. Doing it for tens of thousands of parcels takes hours if you do it naively. So I parallelized the pipeline with Python’s ProcessPoolExecutor.

4. ML-Powered Owner Classification

Raw parcel ownership data is messy. Names don’t tell you whether an owner is an individual, a corporation, a trust, or a government entity. So I built a scikit-learn classification pipeline with an LLM-assisted labeling stage.

5. Hybrid Rendering Frontend

The UI needs both server-rendered pages (for auth, SEO, fast initial load) and highly interactive components (map, chat, 3D viewer). Astro SSR with Preact islands gets both.

Code Snippet: Conversational Search Orchestration

This is how the chat search use case coordinates conversation state, the LLM, and tool execution.

async def execute(self, user_id: UUID, message: str,
                  conversation_id: Optional[UUID] = None) -> ChatSearchResponse:
    # Load or create conversation with full message history
    conversation = await self._get_or_create_conversation(
        user_id, conversation_id
    )

    # Build context-aware prompt with field descriptions and prior filters
    messages = self._build_messages(conversation, message)

    # LLM selects a tool and generates structured arguments
    llm_response = await self.llm_service.chat_with_tools(
        messages=messages,
        tools=self.tool_definitions,  # Dynamically generated from schema
        temperature=0.7,
    )

    # Execute the chosen tool (search, refine, clarify, or stats)
    result = await self._execute_tool_call(llm_response.tool_call)

    # Persist conversation state for multi-turn refinement
    await self._save_turn(conversation, message, llm_response, result)
    return result

Deployment Journey

TALON was developed on a beefy local workstation, essential for processing LiDAR datasets and running the full PostGIS + Redis + application stack during development. The first deployment target was Render, which got the app online quickly but made the limitations of managed PaaS painfully obvious for compute-heavy geospatial work. It now runs on a self-managed Proxmox host on a bare metal dedicated server: significantly more CPU, memory, and storage at a marginal cost bump, full control over the environment, and actual headroom to scale as data coverage expands.

Infrastructure:

Features

Tech Stack