Geospatial diagnostics engine: projection x-ray, cartographic linting, spatial diffing, and autofix. Named after Tissot's indicatrix — the distortion ellipses that reveal what projections hide. Rust core with Python bindings via PyO3. CLI-first, visual-first, zero-config to start.
Read this file completely before doing anything.
Then read ai-dev/architecture.md for full system design.
Then read ai-dev/guardrails/ for hard constraints.
Tissot is NOT another linter that prints text warnings. It is a visual diagnostics engine.
Seven core principles — these are identity, not backlog:
- Visual-first output — The default output is an interactive map in the browser, not terminal text. Every finding has a spatial location rendered on the data. Terminal summaries exist but are secondary.
- Projection X-Ray (hero feature) —
tissot xray data.gpkggenerates a distortion heatmap on your actual features with Tissot ellipses, quantified error metrics, and CRS recommendations. This is the demo. This is the screenshot. This is why people install it. - Autofix —
tissot fixdoesn't just report problems, it fixes them. Reproject to optimal CRS, heal topology gaps, rewrite symbology for WCAG compliance. Theprettier --writeof GIS. - Map Score —
tissot score project.qgzproduces a 0-100 quality score with category breakdown (Projection, Data Quality, Accessibility, Classification). Lighthouse for maps. - Interactive diff slider —
tissot diff v1.gpkg v2.gpkggenerates a before/after slider map, not a text changelog. - Watch mode —
tissot watch ./data/monitors files and streams results to a live browser dashboard. - Zero-config first run — No config file needed.
tissot xray whatever.gpkgworks immediately with smart defaults.
If a feature or design decision conflicts with these principles, the principles win.
When starting a new task:
- Read CLAUDE.md (this file)
- Read ai-dev/architecture.md
- Read ai-dev/guardrails/ for constraints that override all other guidance
- Read the relevant ai-dev/agents/ file for your role
- Check ai-dev/decisions/ for prior decisions that may affect your work
- Check ai-dev/skills/ for domain patterns specific to this project
Before writing code:
- Confirm you understand the module's responsibility
- List the files you will create or modify
- Check ai-dev/decisions/ for prior decisions affecting this module
- Show me the plan
Do not proceed until I type Engage.
| Component | Version | Notes |
|---|---|---|
| Rust | 1.83+ (2024 edition) | Required for PyO3 0.27+ |
| Python | 3.9 – 3.13 | ArcGIS Pro 3.x ships 3.9; target broad range |
| PyO3 | 0.27.x | Python bindings via maturin |
| maturin | 1.x | Build system for Python wheels |
| geo (crate) | latest | GeoRust primitives and algorithms |
| proj (crate) | latest | PROJ bindings for CRS transforms |
| geozero | latest | Primary IO — zero-copy format reading (DL-004) |
| shapefile (crate) | latest | Pure Rust shapefile reader |
| flatgeobuf (crate) | latest | Pure Rust FlatGeobuf reader |
| gdal (crate) | latest | Optional feature — fallback IO + GPKG write |
| clap | 4.x | CLI argument parsing |
| axum | latest | Local web server for visual reports + watch mode |
| askama | latest | HTML template engine for reports |
| wasm-bindgen | latest | Wasm↔JS bridge for browser target (DL-005) |
| wgpu | latest | Phase 2 — WebGPU compute for heatmaps (DL-006) |
tissot/
├── CLAUDE.md # This file — AI reads first
├── README.md # Human-facing project description
├── Cargo.toml # Rust workspace root
├── pyproject.toml # Python package config (maturin)
├── .github/
│ └── copilot-instructions.md # GitHub Copilot project instructions
├── ai-dev/ # AI development infrastructure
│ ├── architecture.md # System design, module interfaces, data flow
│ ├── spec.md # Requirements, acceptance criteria, constraints
│ ├── patterns.md # Code patterns, anti-patterns, lessons learned
│ ├── prompt-templates.md # Reusable AI tool prompts
│ ├── agents/ # Specialized agent configurations
│ ├── decisions/ # Architectural Decision Records (DL-xxx)
│ ├── skills/ # Domain-specific SKILL.md files
│ └── guardrails/ # Hard constraints (override everything)
├── specs/ # Detailed feature specifications
│ ├── projection-xray.md # Hero feature spec
│ ├── map-score.md # Lighthouse-for-maps scoring spec
│ ├── autofix.md # Autofix engine spec
│ ├── visual-diff.md # Interactive diff slider spec
│ ├── cartographic-linter.md # Cartographic checker rules spec
│ └── watch-mode.md # File watching + live dashboard spec
├── src/ # Rust source (library + CLI binary)
│ ├── lib.rs # Library root — re-exports public API
│ ├── main.rs # CLI binary entry point (clap)
│ ├── core/ # Core types, config, rule engine
│ │ ├── mod.rs
│ │ ├── config.rs # .tissot.yml config parsing (optional)
│ │ ├── rule.rs # Rule trait, severity, result types
│ │ ├── report.rs # Finding aggregation
│ │ └── registry.rs # Rule registry
│ ├── checkers/ # Diagnostic rule modules
│ │ ├── mod.rs
│ │ ├── cartography/ # Map quality rules
│ │ ├── projection/ # CRS distortion analysis
│ │ ├── diff/ # Spatial change detection
│ │ └── data_quality/ # Schema, topology, extent checks
│ ├── xray/ # Projection X-Ray engine (hero feature)
│ │ ├── mod.rs
│ │ ├── distortion.rs # Per-feature distortion computation
│ │ ├── heatmap.rs # Distortion heatmap generation
│ │ ├── ellipse.rs # Tissot ellipse rendering on features
│ │ ├── recommend.rs # CRS recommendation with tradeoff analysis
│ │ └── compare.rs # Side-by-side CRS comparison
│ ├── fix/ # Autofix engine
│ │ ├── mod.rs
│ │ ├── reproject.rs # CRS autofix
│ │ ├── topology.rs # Gap/overlap healing
│ │ ├── symbology.rs # WCAG accessibility autofix
│ │ └── schema.rs # Schema normalization
│ ├── score/ # Map quality scoring
│ │ ├── mod.rs
│ │ ├── calculator.rs # Score computation (0-100)
│ │ ├── categories.rs # Category breakdown
│ │ └── badge.rs # SVG badge generation
│ ├── io/ # Format readers/writers (DL-004: geozero-first)
│ │ ├── mod.rs
│ │ ├── geojson.rs # geozero + serde_json (pure Rust)
│ │ ├── geopackage.rs # geozero reader, gdal writer (feature-gated)
│ │ ├── shapefile.rs # shapefile crate (pure Rust)
│ │ ├── flatgeobuf.rs # flatgeobuf crate (pure Rust)
│ │ ├── qgis.rs # .qgz/.qgs project parsing (XML + zip)
│ │ ├── arcgis.rs # .aprx/.mapx parsing
│ │ └── wasm.rs # Byte-array IO for Wasm target (DL-005)
│ ├── wasm/ # Browser entry points (DL-005, cfg(wasm32))
│ │ ├── mod.rs # wasm-bindgen exports
│ │ └── bridge.rs # JS↔Rust data conversion
│ └── report/ # Output renderers
│ ├── mod.rs
│ ├── visual/ # Browser-based visual outputs
│ │ ├── mod.rs
│ │ ├── server.rs # Local axum server for reports
│ │ ├── xray_map.rs # X-Ray interactive map
│ │ ├── diff_slider.rs # Before/after slider map
│ │ ├── findings_map.rs # Diagnostic findings map
│ │ ├── score_dashboard.rs # Score breakdown dashboard
│ │ └── watch_dashboard.rs # Live watch mode dashboard
│ ├── terminal.rs # Rich terminal output (secondary)
│ ├── json.rs # Machine-readable JSON
│ └── sarif.rs # SARIF for CI/CD
├── templates/ # Askama HTML templates
│ ├── xray.html # X-Ray report template
│ ├── diff.html # Diff slider template
│ ├── findings.html # Findings map template
│ ├── score.html # Score dashboard template
│ └── watch.html # Watch mode live dashboard
├── assets/ # Static assets for visual reports
│ ├── tissot.css # Report styling
│ └── tissot.js # Map interaction (Leaflet/MapLibre)
└── python/ # Python bindings (PyO3 + maturin)
└── tissot/
├── __init__.py
├── _tissot.pyi # Type stubs
└── qgis_plugin/ # QGIS plugin wrapper (future)
# Hero feature — Projection X-Ray
tissot xray data.gpkg # Distortion analysis → opens browser map
tissot xray data.gpkg --recommend # Include CRS recommendations
tissot xray data.gpkg --compare 3089 # Side-by-side with EPSG:3089
# Check — diagnostic linting
tissot check data.gpkg # All checks → opens findings map
tissot check project.qgz # Cartographic + data checks on project
tissot check data.gpkg --domain quality # Only data quality checks
tissot check data.gpkg --terminal # Terminal-only output (no browser)
# Score — map quality rating
tissot score project.qgz # 0-100 score → opens dashboard
tissot score data.gpkg --badge score.svg # Generate SVG badge for README
# Diff — visual change detection
tissot diff v1.gpkg v2.gpkg # Interactive slider → opens browser
tissot diff v1.gpkg v2.gpkg --json # Machine-readable diff
# Fix — autofix problems
tissot fix data.gpkg --reproject # Reproject to recommended CRS
tissot fix data.gpkg --topology # Heal gaps and overlaps
tissot fix project.qgz --accessibility # Fix WCAG issues in symbology
# Watch — live monitoring
tissot watch ./data/ # Monitor directory → live dashboard
# Utility
tissot init # Create .tissot.yml with smart defaults
tissot --version
tissot --help- Edition 2024 — Rust 1.83+
- Error handling —
thiserrorfor library,anyhowfor CLI binary only - Geometry types — always use
geocrate primitives - CRS operations — always through
projcrate - Serialization —
serde+serde_jsonfor config and data interchange - Traits over enums — checker rules implement the
Ruletrait - Tests — every module has
#[cfg(test)] mod tests; integration tests intests/ - No
unwrap()in library code — propagate with?, usethiserror - No
println!in library code — uselog+env_logger
- Browser is the primary output — visual commands spawn a local axum server and open the default browser. The server shuts down on Ctrl+C.
- MapLibre GL JS — use MapLibre (not Leaflet) for the interactive maps. It's open source, WebGL-powered, and handles large datasets.
- Self-contained HTML — reports must work offline. Inline all JS/CSS. No CDN dependencies.
- Dark mode default — visual reports use a dark theme that makes data pop.
- Thin wrapper — all computation in Rust, Python is API surface only
- Type stubs — maintain
_tissot.pyifor every public function - ArcPy interop — accept/return WKT, WKB, GeoJSON strings
Tissot has six major subsystems. See ai-dev/architecture.md for full design.
- X-Ray Engine (
src/xray/) — Computes per-feature distortion metrics, generates heatmaps, renders Tissot ellipses, recommends CRS candidates. - Checker Engine (
src/checkers/) — Rule-based diagnostics across four domains: cartography, projection, diff, data quality. - Fix Engine (
src/fix/) — Autofix transformations: reproject, heal topology, fix symbology. - Score Engine (
src/score/) — Aggregates checker results into a 0-100 quality score with category breakdown. - Visual Report Server (
src/report/visual/) — Local axum web server that serves interactive HTML/MapLibre reports. - IO Layer (
src/io/) — Format readers for GeoPackage, GeoJSON, Shapefile, QGIS projects, ArcGIS projects.
- Do NOT make terminal text the primary output — visual map is always the default
- Do NOT require config for first run — zero-config must work
- Do NOT bypass
geocrate types with raw coordinate tuples - Do NOT use
unwrap()orexpect()in library code - Do NOT put computation logic in the Python binding layer
- Do NOT hardcode CRS identifiers — always use config or auto-detection
- Do NOT add dependencies without a DL- decision record
- Do NOT write rules as standalone functions — they must implement the
Ruletrait - Do NOT use CDN-hosted assets in visual reports — everything must be self-contained/offline
- Do NOT assume WGS 84 — always read CRS from the data source
- Do NOT generate boring reports — every visual output should make someone want to screenshot it