The SVG Cave Mapper is a layered application for converting SVG cave survey maps into georeferenced GIS formats. It supports both CLI and GUI interfaces with proper separation of concerns.
┌─────────────────────────────────────────────────────────┐
│ Presentation Layer │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ CLI │ │ GUI │ │
│ │ (argparse) │ │ (PyQt5) │ │
│ └──────────────┘ └──────────────┘ │
│ │ │ │
└─────────┼─────────────────────────────────────┼─────────┘
│ │
┌─────────┴─────────────────────────────────────┴─────────┐
│ Services Layer │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ Processing │ │ File │ │ Geometry │ │
│ │ Service │ │ Service │ │ Service │ │
│ └────────────┘ └────────────┘ └────────────┘ │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ Export │ │ Tile │ │ Tile Cache │ │
│ │ Service │ │ Service │ │ (SQLite) │ │
│ └────────────┘ └────────────┘ └────────────┘ │
└──────────────────────────────────────────────────────────┘
│ │
┌─────────┴─────────────────────────────────────┴─────────┐
│ State Management Layer │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Application │ │ Session │ │ Config │ │
│ │ State │ │ Manager │ │ Manager │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Event Bus (Pub/Sub) │ │
│ └──────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘
│
┌─────────┴─────────────────────────────────────┐
│ Core Domain Layer │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Parsers │ │ Geometry │ │ Models │ │
│ └──────────┘ └──────────┘ └──────────┘ │
└────────────────────────────────────────────────┘
SVGCaveMapProcessor (core/processor.py) orchestrates:
- Parse SVG file → Extract element coordinates via
SVGParserFactory - Normalize coordinates → Ensure (0,0) origin via
CoordinateNormalizer - Build geometries → Convert to Shapely objects via
GeometryBuilder - Return
GeometryDataobjects with metadata and SVG styling
The processor uses parse_element_multi() to handle elements that produce multiple geometries (e.g., paths with multiple subpaths).
SVGParserFactory (parsers/factory.py) dynamically selects parsers for each SVG element type. All parsers implement the SVGElementParser protocol.
Parsers:
- PathParser (
path_parser.py): Handles<path>elements usingsvgpathtools. Splits paths at discontinuities (multipleMcommands) into separate subpaths. Uses adaptive sampling — straight line segments get 2 points, curves scale by length (~1 sample per 5 SVG units, capped at 100). - RectParser (
shape_parsers.py): Handles<rect>with optionalrx/ryrounded corners (8 arc points per corner). - CircleParser / EllipseParser (
shape_parsers.py): 64-point discretization. - LineParser (
shape_parsers.py): Simple two-point lines. - PolygonParser / PolylineParser (
polygon_parser.py): Coordinate list parsing. - TextParser / TspanParser (
text_parser.py): Text position extraction as Point geometry. - UseParser / SymbolParser (
use_parser.py): SVG<use>element resolution.
Multi-geometry support: parse_element_multi() returns a list of coordinate lists per element, enabling proper handling of compound paths.
GeometryBuilder (geometry/builder.py) converts coordinates to Shapely geometries:
- Closed polygons: SVG
<polygon>,<rect>,<circle>,<ellipse>are always treated as closed polygons. Other elements are classified as polygons if first == last coordinate. - Lines: Open coordinate sequences become
LineString. - Markers: Zero-size rects (width=0, height=0) become
Pointgeometries with typemarker(used for survey stations). - Text: Text/tspan elements become
Pointwith text content in attributes. - SVG style extraction: Stroke, fill, stroke-width, stroke-linecap, stroke-linejoin, and opacity are extracted from both direct attributes and the
styleattribute (style takes precedence).
models.py defines:
Point2D: Pixel coordinates with unpacking and offset supportBoundingBox: Bounds calculation from point listsGeometryData: Wraps Shapely geometry + attributes dict (type, id, class, svg_tag, stroke, fill, stroke-width, etc.)
ExportService (services/export_service.py) handles GIS export:
- Converts SVG coordinates to geographic coordinates using geodesic calculations (pyproj Geod on WGS84 ellipsoid)
- Exports to GeoPackage (.gpkg) and Shapefile (.shp) via geopandas
- Separates geometry types (polygons, lines, points) into appropriate layers
- Supports multiple CRS: WGS84, ETRS89/PT TM06, UTM zones, OSGB, JGD2011, and custom EPSG codes
- Uses
pyproj.Transformerfor datum transformations
- TileService (
services/tile_service.py): Fetches map tiles from ESRI World Imagery or OpenStreetMap. Parallel fetching with ThreadPoolExecutor (8 workers). - TileCache (
services/tile_cache.py): Two-tier caching system:- Memory: True LRU cache using
OrderedDict(default 500 tiles) - Disk: SQLite metadata database + image files on disk (default 500MB limit, 30-day TTL)
- Automatic cleanup of expired tiles and size-based eviction
- Memory: True LRU cache using
DeclinationService (services/declination_service.py): Magnetic declination calculation:
- WMM (via pygeomag): World Magnetic Model, most accurate for current dates (2025-2030)
- IGRF (via ppigrf): International Geomagnetic Reference Field, supports historical dates (1900-2030)
- Automatic model selection based on date range
- Returns declination value and secular variation (annual change rate)
- ProcessingService: Bridge to
SVGCaveMapProcessorfor geometry extraction - FileService: SVG file loading, validation, recent files tracking
- GeometryService: Spatial queries (find at point, in bounding box), statistics
- ApplicationState: Central state container (file, control point, geometries, settings, scale, background map config)
- SessionManager: JSON-based session save/restore with all georeferencing parameters (control point pixel + lat/lon, scale, declination, CRS, view settings)
- ConfigManager: User preferences with JSON persistence and default values
- EventBus: Pub/sub event system for loose coupling between components
MainWindow (gui/main_window.py):
- Menu bar: File, View (control point, axes, background map, map type), Export, Tools (ruler, inspect, tile cache)
- Side panel: Info panel, georeferencing panel, export panel
- Footer/status bar: NAMMI branding, SVG coordinates, geographic coordinates
MapCanvas (gui/map_canvas.py):
- SVG-faithful rendering: Uses original SVG stroke colors, fill colors, stroke widths, line caps, and line joins per geometry
- Filled polygons: Polygons with SVG fill attributes are rendered with the original fill color
- Survey station markers: Rendered as small dots
- Background map tiles: Satellite or OSM imagery under the SVG overlay
- Geodesic coordinate conversion:
_world_to_latlon()and_latlon_to_world()use pyproj Geod (Vincenty's formula on WGS84 ellipsoid) - Performance: Cached background pixmap, instant pan via pixmap translation, deferred cache rebuild, LOD simplification during interaction, viewport culling
- CoordinateInputDialog: Modal dialog for lat/lon entry
- GeorefPanel: Declination, scale, control point coordinate editing
- ExportPanel: CRS selection dropdown with custom EPSG input, GeoPackage and Shapefile export buttons
- DeclinationPanel: Magnetic declination calculator with date picker, WMM/IGRF model selection, and auto-apply
~/.svg_cave_mapper/
├── config.json # User preferences
├── recent_files.txt # Recent files list
├── tile_cache/ # Persistent tile cache
│ ├── tile_cache.db # SQLite metadata
│ └── tiles/ # Cached tile images
└── sessions/
└── last_session.json # Auto-saved session
- Cached background rendering: Geometries + tiles rendered to an offscreen pixmap
- Instant pan: During panning, the cached pixmap is translated (zero re-rendering)
- Deferred cache rebuild: Cache is rebuilt during idle time after interaction ends
- LOD simplification: Complex geometries simplified during zoom/pan interaction
- Viewport culling: Only visible geometries are rendered
- Adaptive curve sampling: Lines get 2 points; curves scale by length
- Parallel tile fetching: 8 concurrent workers for tile downloads
- Two-tier tile cache: LRU memory cache + persistent SQLite-tracked disk cache