<div align="center">
  <img src="docs/assets/logo.svg" alt="CitySense Logo" width="320"/>

  <h1>CitySense</h1>

  <p><b>Geospatial RAG and MCP Server Library for Urban AI Development</b></p>
  <i>Specification v0.2.1 | February 2026 | WUF13 Aligned</i>

  <br><br>

  <!-- Package & CI Status -->
  <p>
    <a href="https://pypi.org/project/citysense/"><img src="https://img.shields.io/pypi/v/citysense?logo=pypi&logoColor=white" alt="PyPI version"></a>
    <a href="https://pypi.org/project/citysense/"><img src="https://img.shields.io/pypi/pyversions/citysense?logo=python&logoColor=white" alt="PyPI - Python Version"></a>
    <a href="https://pypi.org/project/citysense/"><img src="https://img.shields.io/pypi/dm/citysense?logo=pypi&logoColor=white" alt="PyPI - Downloads"></a>
    <a href="https://github.com/olaflaitinen/citysense/actions"><img src="https://img.shields.io/github/actions/workflow/status/olaflaitinen/citysense/ci.yml?branch=main&logo=githubactions&logoColor=white" alt="CI"></a>
    <a href="https://codecov.io/gh/olaflaitinen/citysense"><img src="https://codecov.io/gh/olaflaitinen/citysense/graph/badge.svg" alt="codecov"></a>
    <a href="https://scorecard.dev/viewer/?uri=github.com/olaflaitinen/citysense"><img src="https://api.scorecard.dev/projects/github.com/olaflaitinen/citysense/badge" alt="OpenSSF Scorecard"></a>
    <a href="https://citysense.readthedocs.io"><img src="https://img.shields.io/readthedocs/citysense?logo=readthedocs&logoColor=white" alt="Read the Docs"></a>
    <br>
    <a href="LICENSE"><img src="https://img.shields.io/badge/license-EUPL--1.2-green?logo=europeanunion&logoColor=white" alt="License"></a>
    <a href="https://docs.astral.sh/ruff/"><img src="https://img.shields.io/badge/code%20style-ruff-000000?logo=ruff&logoColor=white" alt="Ruff"></a>
    <a href="https://mypy-lang.org/"><img src="https://img.shields.io/badge/type%20checker-mypy-blue?logo=python&logoColor=white" alt="mypy"></a>
    <a href="https://pre-commit.com/"><img src="https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white" alt="Pre-commit"></a>
  </p>

  <!-- Tech Stack & Standards -->
  <p>
    <a href="https://modelcontextprotocol.io/"><img src="https://img.shields.io/badge/protocol-MCP-6366F1" alt="MCP"></a>
    <a href="https://sdgs.un.org/goals/goal11"><img src="https://img.shields.io/badge/UN-SDG%2011-009EDB" alt="SDG 11"></a>
    <a href="https://www.openstreetmap.org/"><img src="https://img.shields.io/badge/data-OpenStreetMap-7EBC6F?logo=openstreetmap&logoColor=white" alt="OpenStreetMap"></a>
    <a href="https://qdrant.tech/"><img src="https://img.shields.io/badge/vector%20store-Qdrant-0094FF?logo=qdrant&logoColor=white" alt="Qdrant"></a>
    <a href="https://shapely.readthedocs.io/"><img src="https://img.shields.io/badge/geometry-Shapely%202.1%2B-3776AB?logo=python&logoColor=white" alt="Shapely"></a>
    <a href="https://geopandas.org/"><img src="https://img.shields.io/badge/geospatial-GeoPandas%201.1%2B-3776AB?logo=python&logoColor=white" alt="GeoPandas"></a>
  </p>
</div>

---

## Table of Contents

1. [Abstract](#abstract)
2. [Executive Summary](#executive-summary)
3. [Overview](#overview)
4. [Architecture](#architecture)
5. [Requirements](#requirements)
6. [Installation](#installation)
7. [Quick Start](#quick-start)
8. [Configuration](#configuration)
9. [CLI Reference](#cli-reference)
10. [MCP Integration](#mcp-integration)
11. [Pilot Countries](#pilot-countries)
12. [Mathematical Foundations](#mathematical-foundations)
13. [API Summary](#api-summary)
14. [Data Sources and Connectors](#data-sources-and-connectors)
15. [WUF13 Alignment](#wuf13-alignment)
16. [SpatialIntent Structure](#spatialintent-structure)
17. [H3 Spatial Index](#h3-spatial-index)
18. [Dependencies](#dependencies)
19. [Data Flow](#data-flow)
20. [Troubleshooting](#troubleshooting)
21. [Glossary](#glossary)
22. [Frequently Asked Questions](#frequently-asked-questions)
23. [Version History](#version-history)
24. [Development](#development)
25. [Security Considerations](#security-considerations)
26. [Performance Notes](#performance-notes)
27. [References](#references)
28. [License](#license)
29. [Links](#links)

---

## Abstract

CitySense is an open-source Python library that bridges geospatial urban data with large language model (LLM) toolchains. It provides two tightly integrated components: a geospatial retrieval-augmented generation (RAG) framework that semantically indexes spatial datasets and retrieves relevant context for natural language queries, and a Model Context Protocol (MCP) server that exposes city intelligence directly into AI-assisted development environments. The library is aligned with the United Nations Sustainable Development Goal 11 (Sustainable Cities and Communities), the New Urban Agenda, and the 13th World Urban Forum (WUF13) dialogue dimensions. CitySense supports five pilot countries (Azerbaijan, Finland, Sweden, Denmark, Norway) with consistent schemas and national coordinate reference systems. The framework implements spectral indices (NDVI, NDWI, NDBI, EVI, MNDWI, BSI) from Sentinel-2 L2A, Reciprocal Rank Fusion for hybrid retrieval, Urban Resilience Composite Score, and SDG 11.3.1 Land Consumption Rate. Street-level imagery (Mapillary, KartaView) and multispectral satellite data (Copernicus Data Space, Sentinel Hub) extend traditional vector geospatial analysis.

---

## Executive Summary

| Aspect | Description |
|--------|-------------|
| Primary function | Semantic geospatial retrieval and MCP server for urban AI |
| Target users | Urban AI developers, smart city researchers, urban policy tools builders |
| Core technologies | Python 3.12+, Qdrant, Shapely, GeoPandas, H3, fastembed, MCP |
| Data sources | OpenStreetMap, Sentinel-2, Mapillary, KartaView, Copernicus Data Space |
| Standards alignment | WUF13, SDG 11, UN New Urban Agenda |
| License | EUPL-1.2 |

---

## Overview

### Design Philosophy

The core design philosophy of CitySense is that a developer should be able to express spatial intent in natural language and receive structured geospatial results without writing spatial query code. For example:

```python
# find housing zones with low resilience scores near transit corridors in Baku
```

CitySense resolves such intent into structured geospatial results drawn from live and indexed sources.

### Observational Layers

CitySense adds a third observational layer on top of traditional vector geospatial data:

| Layer | Source | Purpose |
|-------|--------|---------|
| Vector | OpenStreetMap, national registers | Buildings, roads, land use, amenities |
| Street-level imagery | Mapillary API v4, KartaView REST API | Ground truth, validation |
| Satellite | Copernicus Data Space, Sentinel Hub | Spectral indices, change detection |

### Key Features

| Capability | Description |
|------------|-------------|
| Natural language queries | Intent parsing and semantic retrieval over geospatial knowledge bases |
| Multi-source indexing | OpenStreetMap, Sentinel-2, Mapillary, KartaView, national registers |
| MCP integration | Native Model Context Protocol server for Cursor, Claude, VS Code |
| WUF13 alignment | Pilot countries with consistent schemas and national CRS |
| SDG 11 metrics | Land consumption rate (11.3.1), urban resilience composite score |
| Spectral indices | NDVI, NDWI, NDBI, EVI, MNDWI, BSI from Sentinel-2 L2A |
| Reciprocal Rank Fusion | Hybrid dense and sparse retrieval with configurable parameter k |

### Primary User Groups

| Group | Use Case |
|-------|----------|
| Urban AI Developers | Build applications on city data with semantic access to geospatial knowledge bases |
| Smart City Researchers | Query, compare, and analyze datasets across multiple countries with consistent schemas |
| Urban Policy Tools Builders | Prototype AI-assisted planning tools aligned with UN New Urban Agenda, SDG 11, and WUF13 |

---

## Architecture

### System Stack

```
┌─────────────────────────────────────────────────────────────────────────────┐
│                           CitySense Stack                                    │
├─────────────────────────────────────────────────────────────────────────────┤
│  CLI (citysense)                                                             │
│  pilot init | index build | query | serve | export | imagery | watch         │
├─────────────────────────────────────────────────────────────────────────────┤
│  MCP Server (stdio/SSE)              │  Geospatial RAG Pipeline               │
│  Tools: query_spatial_context,      │  Intent Parser → H3 Filter →            │
│  analyze_housing_zone,              │  Dense Retrieval + Sparse (BM25) →       │
│  get_resilience_score, bounds       │  RRF Fusion → Reranker → Assembler      │
├─────────────────────────────────────────────────────────────────────────────┤
│  Connectors: OSM, Sentinel-2, Mapillary, KartaView, CDSE                    │
├─────────────────────────────────────────────────────────────────────────────┤
│  Vector Store (Qdrant)  │  Geometry (Shapely, GeoPandas, H3)                 │
└─────────────────────────────────────────────────────────────────────────────┘
```

### Module Structure

| Module | Responsibility |
|--------|----------------|
| citysense.cli | Typer-based command-line interface |
| citysense.core | Configuration, session, logging, registry, exceptions |
| citysense.geo | CRS, H3, bbox, geometry, OSM utilities |
| citysense.connectors | OSM, Mapillary, KartaView, Sentinel, base connector |
| citysense.rag | Intent parsing, retriever, embedder, reranker, assembler |
| citysense.imagery | Street and satellite processing |
| citysense.urban | SDG 11 indicators |
| citysense.climate | Resilience scoring |
| citysense.pilot | Country-specific configurations |
| citysense.mcp | MCP server and tools |

### Design Principles

| Principle | Implementation |
|-----------|----------------|
| Schema consistency | Pilot configs enforce national CRS and normalised GeoDataFrame schema |
| Extensibility | Connector and pilot registries allow plug-in modules |
| Observability | structlog for structured logging; configurable log levels |
| Type safety | mypy strict mode; Pydantic for config and data validation |

---

## Requirements

| Requirement | Version |
|--------------|---------|
| Python | 3.12 or 3.13 |
| Qdrant | Default endpoint http://localhost:6333 |
| GDAL | 3.10+ (for rasterio, geopandas; conda recommended) |

---

## Installation

### pip

```bash
pip install citysense
```

### Optional Extras

| Extra | Purpose |
|-------|---------|
| clip | CLIP ViT-B/32 for street imagery embedding |
| sentinelhub | Sentinel Hub Process API |
| dev | ruff, mypy, pytest, mkdocs, pre-commit |

```bash
pip install "citysense[clip]"
pip install "citysense[dev]"
```

### conda-forge (Binary Compatibility)

For binary compatibility with GDAL, PROJ, GEOS:

```bash
conda env create -f environment.yml
conda activate citysense
pip install -e ".[dev]"
```

### Editable Install

```bash
git clone https://github.com/olaflaitinen/citysense.git
cd citysense
pip install -e ".[dev]"
```

---

## Quick Start

### Minimal Workflow

```bash
citysense pilot init fi
citysense index build --city Helsinki --sources osm
citysense query "social housing zones within 1 km of metro stations in Helsinki"
citysense serve --transport stdio
```

### With Imagery (CLIP)

```bash
pip install "citysense[clip]"
citysense pilot init fi
citysense index build --city Helsinki --sources osm,mapillary
```

### With Sentinel Hub

```bash
pip install "citysense[sentinelhub]"
# Set SENTINELHUB_INSTANCE_ID, SENTINELHUB_CLIENT_ID, SENTINELHUB_CLIENT_SECRET
citysense index build --city Baku --sources osm,sentinel
```

---

## Configuration

Configuration is read in priority order: environment variables (prefix `CITYSENSE_`), `.env` file at project root, defaults.

### Environment Variables

| Variable | Default | Description |
|----------|---------|-------------|
| CITYSENSE_PILOT_COUNTRY | None | Pilot module key: az, fi, se, dk, no |
| CITYSENSE_VECTOR_STORE_URL | http://localhost:6333 | Qdrant URL |
| CITYSENSE_EMBEDDING_MODEL | BAAI/bge-m3 | Text embedding model |
| MAPILLARY_ACCESS_TOKEN | None | Mapillary API token |
| CDSE_CLIENT_ID | None | Copernicus Data Space client ID |
| CDSE_CLIENT_SECRET | None | Copernicus Data Space client secret |

### Example .env

```
CITYSENSE_PILOT_COUNTRY=fi
CITYSENSE_VECTOR_STORE_URL=http://localhost:6333
MAPILLARY_ACCESS_TOKEN=your_token
CDSE_CLIENT_ID=your_id
CDSE_CLIENT_SECRET=your_secret
```

---

## CLI Reference

| Command | Description |
|---------|-------------|
| citysense pilot init &lt;country&gt; | Initialize pilot configuration |
| citysense index build --city &lt;name&gt; --sources &lt;list&gt; | Build vector index |
| citysense query &lt;natural language&gt; | Execute semantic spatial query |
| citysense serve --transport stdio | Start MCP server (stdio) |
| citysense serve --transport sse | Start MCP server (SSE) |
| citysense export &lt;format&gt; | Export indexed data |
| citysense imagery &lt;subcommand&gt; | Street/satellite imagery operations |
| citysense watch | Watch mode for real-time updates |

### CLI Examples

```bash
# Initialize Finland pilot
citysense pilot init fi

# Build index for Helsinki from OSM only
citysense index build --city Helsinki --sources osm

# Build index with multiple sources (requires tokens)
citysense index build --city Baku --sources osm,mapillary,sentinel

# Query with natural language
citysense query "metro stations within 500m of parks in Stockholm"

# Start MCP server for Cursor/Claude
citysense serve --transport stdio

# Start MCP server with SSE for web clients
citysense serve --transport sse
```

---

## MCP Integration

### Cursor

Add to `.cursor/mcp.json`:

```json
{
  "mcpServers": {
    "citysense": {
      "command": "citysense",
      "args": ["serve", "--transport", "stdio"],
      "env": {
        "CITYSENSE_PILOT_COUNTRY": "fi",
        "CITYSENSE_VECTOR_STORE_URL": "http://localhost:6333",
        "MAPILLARY_ACCESS_TOKEN": "your_token",
        "CDSE_CLIENT_ID": "your_id",
        "CDSE_CLIENT_SECRET": "your_secret"
      }
    }
  }
}
```

### Claude Desktop

Add to `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "citysense": {
      "command": "citysense",
      "args": ["serve", "--transport", "stdio"],
      "env": {
        "CITYSENSE_PILOT_COUNTRY": "az",
        "CITYSENSE_VECTOR_STORE_URL": "http://localhost:6333"
      }
    }
  }
}
```

### Prerequisites

1. Qdrant running at configured URL
2. Index built: `citysense index build --city Helsinki --sources osm`
3. API tokens for Mapillary and CDSE if using imagery

### MCP Tools

| Tool | Description |
|------|-------------|
| query_spatial_context | Execute natural language spatial query and return GeoJSON context |
| analyze_housing_zone | Analyze housing zone with resilience and SDG metrics |
| get_resilience_score | Compute URCS for a given location or zone |
| get_bounds | Return bounding box for pilot city or region |

### Transport Modes

| Transport | Use case |
|-----------|----------|
| stdio | Cursor, Claude Desktop, local scripts |
| sse | Web applications, remote clients |

---

## Pilot Countries

| Country | Module Key | National CRS | Primary City |
|---------|------------|--------------|--------------|
| Azerbaijan | az | EPSG:32638 | Baku |
| Finland | fi | EPSG:3067 | Helsinki |
| Sweden | se | EPSG:3006 | Stockholm |
| Denmark | dk | EPSG:25832 | Copenhagen |
| Norway | no | EPSG:25833 | Oslo |

### Pilot Configuration Attributes

| Attribute | Description |
|-----------|-------------|
| country | ISO2 code |
| language | IETF BCP 47 tag |
| national_crs | EPSG string |
| default_cities | City name to BBox mapping |
| connector_priority | Ordered connector IDs |
| wuf13_primary_dimensions | WUF13 dimension tags |
| data_gaps | Known gaps and fallback strategies |
| informality_heuristic | SAR/spectral heuristic for tenure |

---

## Mathematical Foundations

### Sentinel-2 Band Reference

All spectral indices are computed from Sentinel-2 L2A surface reflectance bands.

| Band ID | Centre Wavelength (nm) | Resolution (m) |
|---------|------------------------|----------------|
| B02 | 490 (Blue) | 10 |
| B03 | 560 (Green) | 10 |
| B04 | 665 (Red) | 10 |
| B08 | 842 (NIR broad) | 10 |
| B11 | 1610 (SWIR-1) | 20 |
| B12 | 2190 (SWIR-2) | 20 |

### Spectral Indices

#### NDVI (Normalized Difference Vegetation Index)

$$\mathrm{NDVI} = \frac{\rho_{\mathrm{NIR}} - \rho_{\mathrm{Red}}}{\rho_{\mathrm{NIR}} + \rho_{\mathrm{Red}}} = \frac{B_{08} - B_{04}}{B_{08} + B_{04}}$$

Range: $[-1, +1]$. Urban green: $> 0.3$. Built-up: $< 0.1$.

#### NDWI (McFeeters 1996)

$$\mathrm{NDWI} = \frac{B_{03} - B_{08}}{B_{03} + B_{08}}$$

Open water: $> 0.0$.

#### NDBI (Zha et al. 2003)

$$\mathrm{NDBI} = \frac{B_{11} - B_{08}}{B_{11} + B_{08}}$$

Built-up: $> 0.0$.

#### EVI (Huete et al. 2002)

$$\mathrm{EVI} = 2.5 \cdot \frac{B_{08} - B_{04}}{B_{08} + 6 \cdot B_{04} - 7.5 \cdot B_{02} + 1}$$

#### MNDWI (Modified NDWI)

$$\mathrm{MNDWI} = \frac{B_{03} - B_{11}}{B_{03} + B_{11}}$$

#### BSI (Rikimaru et al. 2002)

$$\mathrm{BSI} = \frac{(B_{11} + B_{04}) - (B_{08} + B_{02})}{(B_{11} + B_{04}) + (B_{08} + B_{02})}$$

### Reciprocal Rank Fusion

$$\mathrm{RRF}(d) = \sum_{R \in \{R_{\mathrm{dense}}, R_{\mathrm{sparse}}\}} \frac{1}{k + \mathrm{rank}_R(d)}$$

Default: $k = 60$.

### Cross-Encoder Reranking

$$s_i = \mathrm{CrossEncoder}(q, c_i)$$

### Urban Resilience Composite Score

$$\mathrm{URCS}(c) = \sum_{d \in D} w_d \cdot R_d(c)$$

where $D = \{\mathrm{physical}, \mathrm{climate}, \mathrm{social}, \mathrm{infrastructure}\}$.

| Dimension | Weight $w_d$ |
|-----------|--------------|
| Physical | 0.30 |
| Climate | 0.30 |
| Social | 0.20 |
| Infrastructure | 0.20 |

#### Physical Resilience Sub-dimension

$$R_{\mathrm{physical}} = \alpha_s \cdot s_{\mathrm{condition}} + \alpha_a \cdot (1 - a_{\mathrm{norm}}) + \alpha_{\mathrm{SAR}} \cdot (1 - I_{\mathrm{informal}})$$

Default: $\alpha_s = 0.4$, $\alpha_a = 0.35$, $\alpha_{\text{SAR}} = 0.25$.

### Segregation Indices

#### Dissimilarity Index

$$D = \frac{1}{2} \sum_{i=1}^{n} \left| \frac{g_i}{G} - \frac{m_i}{M} \right|$$

#### Isolation Index

$$P^* = \sum_{i=1}^{n} \left[ \frac{g_i}{G} \cdot \frac{g_i}{t_i} \right]$$

### Transit Accessibility (SDG 11.2.1)

$$A(c) = \sum_{s \in S(c, r)} f(s) \cdot e^{-\lambda \cdot d(c, s)}$$

Default: $r = 800$ m, $\lambda = 0.003$.

### Land Consumption Rate (SDG 11.3.1)

$$\mathrm{SDG}_{11.3.1} = \frac{\mathrm{LCR}}{\mathrm{PGR}}$$

$$\mathrm{LCR} = \frac{\ln(U_{\mathrm{land}}(t_1) / U_{\mathrm{land}}(t_0))}{t_1 - t_0}$$

$$\mathrm{PGR} = \frac{\ln(P(t_1) / P(t_0))}{t_1 - t_0}$$

Sustainable urban growth: ratio approximately 1. Ratio $> 1$: land consumption outpacing population growth. Ratio $< 1$: increasing density.

### Formula Summary Table

| Formula | Parameters | Default |
|---------|------------|---------|
| RRF | k | 60 |
| Transit accessibility | r (radius), λ (decay) | 800 m, 0.003 |
| URCS physical | α_s, α_a, α_SAR | 0.4, 0.35, 0.25 |
| URCS dimensions | w_physical, w_climate, w_social, w_infrastructure | 0.30, 0.30, 0.20, 0.20 |

---

## API Summary

### Intent Parsing

```python
from citysense.rag.intent import parse_intent, SpatialIntent

intent: SpatialIntent = parse_intent(
    "social housing zones within 1 km of metro stations in Helsinki"
)
# intent.entity_types, intent.spatial_relations, intent.city_scope, ...
```

### Spectral Indices

```python
from citysense.imagery.satellite.indices import compute_index, compute_ndvi

# From band dict (B02, B03, B04, B08, B11)
ndvi_array = compute_index("NDVI", bands)
ndwi_array = compute_index("NDWI", bands)
```

### Urban Resilience

```python
from citysense.climate.resilience import compute_urcs, compute_physical_resilience

urcs = compute_urcs(physical=0.7, climate=0.6, social=0.5, infrastructure=0.8)
physical = compute_physical_resilience(
    street_condition=0.8, building_age_norm=0.3, informality_score=0.1
)
```

### SDG 11 Indicators

```python
from citysense.urban.sdg11 import compute_sdg_1131, compute_land_consumption_rate

lcr = compute_land_consumption_rate(
    urban_land_t0_km2=100, urban_land_t1_km2=120, years=10
)
ratio = compute_sdg_1131(
    urban_land_t0_km2=100, urban_land_t1_km2=120,
    pop_t0=1e6, pop_t1=1.2e6, years=10
)
```

### Pilot Configuration

```python
from citysense.pilot import get_pilot_config

config = get_pilot_config("fi")
# config.country, config.national_crs, config.default_cities, ...
```

---

## Data Sources and Connectors

| Connector | Data Type | API |
|-----------|-----------|-----|
| OSM | Vector (buildings, roads, land use) | Overpass API |
| Sentinel-2 | Multispectral imagery | Copernicus Data Space, Sentinel Hub |
| Mapillary | Street-level imagery | Mapillary API v4 |
| KartaView | Street-level imagery | KartaView REST API |
| National registers | Cadastral, tenure | Country-specific |

### Connector Priority

Pilot configurations define `connector_priority` to resolve conflicts when multiple sources provide overlapping data.

---

## WUF13 Alignment

The 13th World Urban Forum (WUF13) dialogue dimensions inform CitySense pilot configurations and indicator coverage.

### WUF13 Dialogue Dimensions

| Dimension | Description | CitySense Coverage |
|-----------|-------------|-------------------|
| Adequate housing | Access to safe, affordable housing | Housing zone analysis, tenure heuristics |
| Urban planning | Land use, zoning, spatial planning | OSM land use, pilot city schemas |
| Climate action | Resilience, adaptation, mitigation | URCS, spectral indices, flood/heat risk |
| Urban economy | Employment, economic opportunity | Transit accessibility, service proximity |
| Urban ecology | Green space, biodiversity | NDVI, NDWI, built-up indices |
| Urban governance | Participation, transparency | Data standards, open schemas |
| Urban finance | Revenue, expenditure, investment | Indicator framework for fiscal analysis |

### Pilot-Specific Data Gaps

| Country | Known Gaps | Fallback Strategy |
|---------|------------|-------------------|
| Azerbaijan | Peri-urban tenure | SAR texture + NDBI heuristic for informality |
| Finland | Informal settlements | Not applicable; use tenure from registers |
| Sweden | (Per national profile) | (Per national profile) |
| Denmark | (Per national profile) | (Per national profile) |
| Norway | (Per national profile) | (Per national profile) |

---

## SpatialIntent Structure

The `SpatialIntent` dataclass captures parsed query slots from natural language input.

| Attribute | Type | Description |
|-----------|------|-------------|
| entity_types | tuple[str, ...] | OSM-style feature types (e.g. bus_stop, railway_station) |
| spatial_relations | tuple[str, ...] | Relations (e.g. near, within_500m) |
| attributes | dict[str, str] | Attribute filters (e.g. resilience: low) |
| country_scope | str \| None | ISO2 country code |
| city_scope | str \| None | City name |
| bbox | BBox \| None | Resolved bounding box |
| wuf13_dimension | str \| None | WUF13 dialogue dimension |
| sdg_indicator | str \| None | SDG indicator code (e.g. 11.2.1) |

---

## H3 Spatial Index

CitySense uses Uber H3 for hexagonal spatial indexing. Benefits include uniform cell size, hierarchical resolution, and efficient neighborhood queries.

### Resolution Reference

| Resolution | Avg hexagon area (km²) | Use case |
|------------|------------------------|----------|
| 5 | 252.9 | City-scale analysis |
| 6 | 36.1 | District-scale |
| 7 | 5.2 | Neighborhood-scale |
| 8 | 0.74 | Block-scale |
| 9 | 0.11 | Building-scale |

### Antimeridian Handling

H3 cells crossing the antimeridian require special handling. CitySense pre-filters queries to avoid invalid geometries.

---

## Dependencies

### Core Dependencies

| Package | Version | Purpose |
|---------|---------|---------|
| shapely | >=2.1.2, <3.0 | Geometry operations |
| geopandas | >=1.1.2, <2.0 | Geospatial DataFrames |
| pyproj | >=3.7.1, <4.0 | Coordinate transformations |
| pyogrio | >=0.10.0 | Vector I/O |
| numpy | >=2.2.0, <3.0 | Numerical arrays |
| pandas | >=2.3.0, <3.0 | Tabular data |
| h3 | >=4.1.0, <5.0 | Hexagonal spatial index |
| rasterio | >=1.4.3, <2.0 | Raster I/O |
| xarray | >=2023.1.0 | Multidimensional arrays |
| pydantic | >=2.12.0, <3.0 | Data validation |
| pydantic-settings | >=2.7.0, <3.0 | Configuration |
| qdrant-client | >=1.17.0, <2.0 | Vector store client |
| fastembed | >=0.4.2, <1.0 | Embedding models |
| rank-bm25 | >=0.2.2 | BM25 sparse retrieval |
| litellm | >=1.57.0, <2.0 | LLM abstraction |
| mcp | >=1.4.0, <2.0 | Model Context Protocol |
| pystac-client | >=0.8.5, <1.0 | STAC catalog access |
| rio-cogeo | >=5.4.0, <6.0 | Cloud-optimized GeoTIFF |
| httpx | >=0.28.0, <1.0 | HTTP client |
| Pillow | >=11.1.0, <12.0 | Image processing |
| APScheduler | >=3.11.0, <4.0 | Task scheduling |
| structlog | >=25.1.0, <26.0 | Structured logging |
| typer | >=0.15.0, <1.0 | CLI framework |

### Optional Dependencies

| Extra | Packages | Purpose |
|-------|----------|---------|
| clip | transformers, torch | CLIP ViT-B/32 for street imagery |
| sentinelhub | sentinelhub | Sentinel Hub Process API |
| dev | ruff, mypy, pytest, mkdocs, pre-commit | Development tooling |

---

## Data Flow

### Index Build Flow

```
OSM Overpass / STAC / Connectors
        │
        ▼
Geometry + Metadata (GeoDataFrame)
        │
        ▼
H3 Cell Assignment (resolution 7 default)
        │
        ▼
Chunking + Text Serialization
        │
        ▼
Dense Embedding (fastembed) + Sparse (BM25)
        │
        ▼
Qdrant Upsert (vectors + payload)
```

### Query Flow

```
Natural Language Query
        │
        ▼
parse_intent() → SpatialIntent
        │
        ▼
H3 Filter (bbox → H3 cells)
        │
        ▼
Dense + Sparse Retrieval (top-k each)
        │
        ▼
RRF Fusion (k=60)
        │
        ▼
Reranker (optional)
        │
        ▼
Assembled Context (GeoJSON, summary)
```

---

## Troubleshooting

| Issue | Cause | Resolution |
|-------|-------|------------|
| Qdrant connection refused | Qdrant not running | Start Qdrant: `docker run -p 6333:6333 qdrant/qdrant` |
| Index empty after build | Connector returned no data | Check city name, pilot config, OSM coverage |
| Mapillary 401 | Invalid or missing token | Set MAPILLARY_ACCESS_TOKEN |
| CDSE auth failure | Invalid credentials | Set CDSE_CLIENT_ID, CDSE_CLIENT_SECRET |
| CRS transform error | Mismatched EPSG | Verify pilot national_crs |
| H3 antimeridian error | Query crosses date line | Use bbox that does not span antimeridian |

---

## Glossary

| Term | Definition |
|------|------------|
| RAG | Retrieval-Augmented Generation; augmenting LLM context with retrieved documents |
| MCP | Model Context Protocol; standard for AI tools and context |
| H3 | Uber hexagonal hierarchical spatial index |
| CRS | Coordinate Reference System |
| URCS | Urban Resilience Composite Score |
| LCR | Land Consumption Rate (SDG 11.3.1 numerator) |
| PGR | Population Growth Rate (SDG 11.3.1 denominator) |
| RRF | Reciprocal Rank Fusion |
| NDVI | Normalized Difference Vegetation Index |
| NDWI | Normalized Difference Water Index |
| NDBI | Normalized Difference Built-up Index |
| WUF13 | 13th World Urban Forum |
| SDG 11 | UN Sustainable Development Goal 11 (Sustainable Cities) |

---

## Frequently Asked Questions

### Is CitySense suitable for production use?

CitySense is in Alpha (Development Status 3). Use for research, prototyping, and pilot deployments. Production hardening is ongoing.

### Which embedding model is used?

Default: BAAI/bge-m3. Configurable via CITYSENSE_EMBEDDING_MODEL.

### Does CitySense support private repositories?

The MCP server and CLI work with any data source. Vector store (Qdrant) can be self-hosted. No data is sent to external services except configured APIs (Mapillary, CDSE, etc.).

### How is coverage computed?

Coverage excludes connectors and modules under active development. Run `pytest tests/unit --cov=citysense --cov-report=term-missing` for current metrics.

### Can I add a new pilot country?

Yes. Implement a PilotConfig in `citysense.pilot`, register in the pilot registry, and add to the CLI init options. See CONTRIBUTING.md.

---

## Version History

| Version | Date | Highlights |
|---------|------|------------|
| 0.2.0 | 2026-02-28 | WUF13 alignment, five pilot countries, SDG 11 indicators, mathematical foundations |
| Unreleased | - | License EUPL-1.2, initial structure per Specification v0.2.0 |

---

## Development

### Setup

```bash
git clone https://github.com/olaflaitinen/citysense.git
cd citysense
pip install -e ".[dev,clip]"
pre-commit install
```

### Linting and Type Checking

```bash
ruff check src tests
ruff format --check src tests
mypy src/citysense
```

### Tests

```bash
pytest tests/unit -v
pytest tests/unit -v --cov=citysense --cov-report=term-missing
```

### Documentation

```bash
mkdocs serve
```

### Priority Contribution Areas

| Area | Description |
|------|-------------|
| Country connectors | Additional WUF13-relevant cities and national data sources |
| Non-Latin script | Arabic, Cyrillic handling for Azerbaijan and Central Asia |
| Informal settlement detection | Model improvements for tenure heuristics |
| Climate adaptation | Document parsers for national plans |

### Python Version Compatibility

| Python | Status |
|--------|--------|
| 3.12 | Supported, tested in CI |
| 3.13 | Supported, tested in CI |
| 3.11 | Not supported (requires 3.12+) |

### Commit Message Format

| Type | Version Bump | Example |
|------|---------------|---------|
| feat: | Minor | feat(imagery): add Sentinel-3 LST connector |
| fix: | Patch | fix(rag): correct H3 pre-filter for antimeridian |
| feat!: | Major | feat!: remove deprecated search() method |
| docs:, chore:, test: | None | docs: add Baku pilot tutorial |

---

## Security Considerations

| Concern | Mitigation |
|--------|------------|
| API tokens | Store in environment variables or secrets; never commit |
| Qdrant | Run locally or in private network; no default auth |
| Data provenance | All connectors document source and license |
| Dependency audit | Dependabot enabled; run `pip audit` periodically |

---

## Performance Notes

| Operation | Typical scale | Notes |
|-----------|---------------|-------|
| Index build (OSM, city) | 10k–100k features | Depends on city size, H3 resolution |
| Query latency | 100–500 ms | Dense + sparse + RRF; reranker adds ~50 ms |
| Embedding | BAAI/bge-m3 | ~50 docs/s on CPU; GPU accelerates |
| Qdrant | Local | Sub-10 ms for vector search at city scale |

---

## References

### Spectral Indices

- McFeeters, S. K. (1996). The use of the Normalized Difference Water Index (NDWI) in the delineation of open water features. International Journal of Remote Sensing, 17(7), 1425-1432.
- Zha, Y., Gao, J., & Ni, S. (2003). Use of normalized difference built-up index in automatically mapping urban areas from TM imagery. International Journal of Remote Sensing, 24(3), 583-594.
- Huete, A., et al. (2002). Overview of the radiometric and biophysical performance of the MODIS vegetation indices. Remote Sensing of Environment, 83(1-2), 195-213.
- Rikimaru, A., Roy, P. S., & Miyatake, S. (2002). Tropical forest cover density mapping. Tropical Ecology, 43(1), 39-47.

### SDG and Urban Indicators

- UN-Habitat. (2020). SDG Indicator 11.3.1 - Land consumption rate. United Nations.
- UN-Habitat. (2020). SDG Indicator 11.2.1 - Proportion of population with convenient access to public transport. United Nations.

### Retrieval

- Cormack, G. V., Clarke, C. L., & Buettcher, S. (2009). Reciprocal rank fusion outperforms condorcet and individual rank learning methods. SIGIR 2009.

### Citation

If you use CitySense in academic work, please cite:

```
CitySense: Geospatial RAG and MCP Server for Urban AI Development.
Specification v0.2.0. WUF13 Aligned. 2026.
https://github.com/olaflaitinen/citysense
```

### Spectral Index Interpretation Ranges

| Index | Low | Medium | High | Interpretation |
|-------|-----|--------|------|-----------------|
| NDVI | < 0.1 | 0.1–0.3 | > 0.3 | Vegetation density |
| NDWI | < 0 | 0 | > 0 | Open water presence |
| NDBI | < 0 | 0 | > 0 | Built-up intensity |
| EVI | < 0.2 | 0.2–0.4 | > 0.4 | Enhanced vegetation |
| BSI | < 0 | 0 | > 0 | Bare soil / impervious |

---

## License

EUPL-1.2. See [LICENSE](LICENSE).

---

## Links

| Resource | URL |
|----------|-----|
| Documentation | [citysense.readthedocs.io](https://citysense.readthedocs.io) |
| Repository | [github.com/olaflaitinen/citysense](https://github.com/olaflaitinen/citysense) |
| Issues | [github.com/olaflaitinen/citysense/issues](https://github.com/olaflaitinen/citysense/issues) |
| Changelog | [CHANGELOG.md](CHANGELOG.md) |
| Contributing | [CONTRIBUTING.md](CONTRIBUTING.md) |
| Security | [SECURITY.md](SECURITY.md) |

---

## Acknowledgments

CitySense builds on open standards and community data: OpenStreetMap contributors, Copernicus Programme, Mapillary, KartaView, Qdrant, and the Model Context Protocol initiative. Pilot country configurations align with national spatial data infrastructures and WUF13 dialogue priorities.
