Skip to main content

API Reference

Complete reference for the Chart Manager API.

Working Examples

All code snippets in this reference are extracted from working examples in docs/examples/. You can run them with go run main.go.

Query vs Download

The chart manager provides different methods depending on whether you need metadata or actual chart data.

Query Catalog (Metadata Only)

Use QueryCatalog() to browse available charts without downloading them:

// Query catalog (NO downloads)
entries := mgr.QueryCatalog(viewport)
for _, entry := range entries {
fmt.Printf("%s - %.1f MB\n", entry.Name, entry.SizeMB)
}

Returns: []CatalogEntry (metadata: name, size, bounds, scale)

Use when:

  • Browsing available charts
  • Showing preview/selection UI to users
  • Planning downloads
  • Bandwidth-conscious applications
  • Building chart discovery features
Query First

Always use QueryCatalog() when you only need metadata. It's instant and requires no downloads.

Download Charts

Use GetChartsForViewport() or GetChart() to download and load charts:

// Load charts (DOWNLOADS on-demand)
charts, _ := mgr.GetChartsForViewport(viewport, zoom)
for _, chart := range charts {
features := chart.FeaturesInBounds(viewport)
// ... render features
}

Returns: []*s57.Chart (fully parsed chart objects)

Use when:

  • Rendering charts
  • Extracting features for display
  • Analyzing chart data
  • Ready to work with actual chart content
Automatic Downloads

GetChartsForViewport() and GetChart() download charts automatically. Each chart may be 0.1-50MB. Use QueryCatalog() first if you want to preview sizes.

Comparison Table

MethodDownloads?ReturnsUse When...
QueryCatalog()NoMetadataBrowsing, planning, previewing
GetChartsForViewport()YesParsed chartsRendering, analyzing features
GetChart()YesSingle chartLoading specific known chart

Choosing the Right Method

Bandwidth-Conscious Workflow:

Runnable Example

See docs/examples/query-before-download for complete implementation

// Step 1: Query to see what's available (no download)
entries := mgr.QueryCatalog(viewport)
fmt.Printf("Found %d charts, %.1f MB total\n", len(entries), totalSize(entries))

// Step 2: User confirms, then download
if userConfirms {
charts, _ := mgr.GetChartsForViewport(viewport, zoom)
}

Direct Loading (when you know you need the charts):

Runnable Example

See docs/examples/quick-start for complete implementation

// Skip query if you're sure you need the data
charts, _ := mgr.GetChartsForViewport(viewport, zoom)

Core Types

ChartManager

The main entry point for chart management.

type ChartManager struct {
// Internal state
}

func NewChartManager(opts ChartManagerOptions) (*ChartManager, error)
func DefaultChartManagerOptions() ChartManagerOptions

Methods:

// Get charts covering a viewport at a specific zoom level
func (m *ChartManager) GetChartsForViewport(viewport s57.Bounds, zoom int) ([]*s57.Chart, error)

// Get a specific chart by name
func (m *ChartManager) GetChart(name string) (*s57.Chart, error)

// Query catalog for chart metadata (no download)
func (m *ChartManager) QueryCatalog(viewport s57.Bounds) []CatalogEntry

// Force catalog update from NOAA
func (m *ChartManager) UpdateCatalog() error

// Get total chart count in catalog
func (m *ChartManager) ChartCount() int

// Get underlying chart loader
func (m *ChartManager) Loader() *ChartLoader

// Get manager statistics
func (m *ChartManager) Stats() ChartManagerStats

ChartManagerOptions

Configure chart manager behavior.

type ChartManagerOptions struct {
// CacheSize sets maximum in-memory cache size in bytes.
// Default: 512MB
CacheSize int64

// CatalogPath overrides the default XDG catalog location.
// Default: ~/.cache/chartmanager/ENCProdCat_19115.xml
CatalogPath string

// ChartCacheDir overrides the default XDG chart cache location.
// Default: ~/.cache/chartmanager/charts
ChartCacheDir string

// MaxCatalogAge sets how old the catalog can be before re-downloading.
// Default: 7 days
MaxCatalogAge time.Duration

// ForceUpdate forces catalog re-download even if cached version is recent.
// Default: false
ForceUpdate bool

// KeepExtracted controls whether to extract chart files to disk or stream from zip.
// If true: Extract files to disk (18MB for 12 charts)
// If false: Stream from zip in memory (5.8MB, 67% savings)
// Default: true
KeepExtracted bool

// OnProgress is called during chart operations to report progress.
// Useful for CLI progress bars or logging.
// Default: nil (no progress reporting)
OnProgress ProgressCallback
}

Example:

opts := chartmanager.DefaultChartManagerOptions()
opts.CacheSize = 1024 * 1024 * 1024 // 1GB
opts.MaxCatalogAge = 24 * time.Hour // Refresh daily
opts.KeepExtracted = false // Save disk space

mgr, err := chartmanager.NewChartManager(opts)

ProgressCallback

Track download and loading progress for charts.

type ProgressCallback func(event ProgressEvent)

type ProgressEvent struct {
ChartName string // Chart being processed (e.g., "US5CA13M")
Status string // "downloading", "loading", "complete", "error"
Message string // Human-readable message
Progress float64 // 0.0 to 1.0 (0% to 100%)
Error error // Error if Status is "error"
}

Status Values:

  • "downloading": Chart is being downloaded from NOAA (first access)
  • "loading": Chart is being loaded from disk cache (subsequent access)
  • "complete": Chart loaded successfully
  • "error": Failed to load chart (see Error field)

Example with CLI progress indicator:

opts := chartmanager.DefaultChartManagerOptions()

// Add progress callback with spinner
spinnerFrames := []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}
spinnerState := make(map[string]int)

opts.OnProgress = func(event chartmanager.ProgressEvent) {
switch event.Status {
case "downloading":
frame := spinnerFrames[spinnerState[event.ChartName] % len(spinnerFrames)]
spinnerState[event.ChartName]++
fmt.Printf("\r %s %s downloading...", frame, event.ChartName)
case "loading":
frame := spinnerFrames[spinnerState[event.ChartName] % len(spinnerFrames)]
spinnerState[event.ChartName]++
fmt.Printf("\r %s %s loading from cache...", frame, event.ChartName)
case "complete":
fmt.Printf("\r ✓ %s loaded\n", event.ChartName)
case "error":
fmt.Printf("\r ✗ %s failed: %v\n", event.ChartName, event.Error)
}
}

mgr, _ := chartmanager.NewChartManager(opts)

Example with logging:

opts.OnProgress = func(event chartmanager.ProgressEvent) {
log.Printf("[%s] %s - %s (%.0f%%)",
event.Status,
event.ChartName,
event.Message,
event.Progress*100)
}
Cached vs Downloaded

The progress callback distinguishes between actual downloads ("downloading") and cached loads ("loading"). This helps users understand whether charts are being fetched from NOAA or loaded from local cache.

ChartManagerStats

Statistics about the chart manager.

type ChartManagerStats struct {
CatalogCharts int // Total charts in catalog
IndexedCharts int // Charts in spatial index
CachedCharts int // Charts in memory cache
CacheHits int // Number of cache hits
CacheMisses int // Number of cache misses
CacheMemory int64 // Current cache memory usage
MaxMemory int64 // Maximum cache memory limit
CatalogPath string // Path to catalog file
ChartCacheDir string // Path to chart cache directory
}

// Get cache hit rate (0.0 to 1.0)
func (s ChartManagerStats) CacheHitRate() float64

CatalogEntry

Metadata for a chart in the NOAA catalog.

type CatalogEntry struct {
Name string // Chart identifier (e.g., "US5MA22M")
Title string // Human-readable title
Bounds s57.Bounds // Geographic bounds
Scale int // Compilation scale
SizeMB float64 // File size in megabytes
URL string // Download URL
}

// Get IHO usage band for this chart
func (e *CatalogEntry) UsageBand() s57.UsageBand

// Get geographic bounds as s57.Bounds
func (e *CatalogEntry) Bounds() s57.Bounds

ChartCatalog

The complete NOAA chart catalog.

type ChartCatalog struct {
Entries []CatalogEntry
}

// Query catalog for charts intersecting bounds
func (c *ChartCatalog) Query(bounds s57.Bounds) []CatalogEntry

// Query by usage band
func (c *ChartCatalog) QueryByBand(bounds s57.Bounds, band s57.UsageBand) []CatalogEntry

// Get entry by chart name
func (c *ChartCatalog) GetByName(name string) (CatalogEntry, bool)

Functions:

// Download catalog from NOAA
func DownloadCatalog(savePath string) (*ChartCatalog, error)

// Load catalog from disk
func LoadCatalog(path string) (*ChartCatalog, error)

// Get default catalog path
func DefaultCatalogPath() (string, error)

// Get default chart cache directory
func DefaultChartCacheDir() (string, error)

Usage Bands

Charts are classified by IHO usage bands based on scale:

type UsageBand int

const (
UsageBandUnknown UsageBand = 0
UsageBandOverview UsageBand = 1 // 1:3M and smaller
UsageBandGeneral UsageBand = 2 // 1:500K to 1:3M
UsageBandCoastal UsageBand = 3 // 1:150K to 1:500K
UsageBandApproach UsageBand = 4 // 1:50K to 1:150K
UsageBandHarbor UsageBand = 5 // 1:25K to 1:50K
UsageBandBerthing UsageBand = 6 // >1:25K
)

// Convert zoom level to usage band
func ZoomToUsageBand(zoom int) UsageBand

// Convert usage band to zoom range
func UsageBandToZoomRange(band UsageBand) (minZoom, maxZoom int)

Zoom Level Mapping:

  • Zoom 1-6 → Overview
  • Zoom 7-9 → General
  • Zoom 10-12 → Coastal
  • Zoom 13-15 → Approach
  • Zoom 16-18 → Harbor
  • Zoom 19+ → Berthing

Common Patterns

Basic Initialization

mgr, err := chartmanager.NewChartManager(
chartmanager.DefaultChartManagerOptions(),
)
if err != nil {
log.Fatal(err)
}

fmt.Printf("Ready: %d charts indexed\n", mgr.ChartCount())

Query Before Download

// Check what charts are available
viewport := s57.Bounds{
MinLon: -122.5, MaxLon: -122.0,
MinLat: 37.5, MaxLat: 38.0,
}

entries := mgr.QueryCatalog(viewport)
fmt.Printf("Found %d charts\n", len(entries))

for _, entry := range entries {
fmt.Printf(" %s (%.1f MB) - Scale 1:%d\n",
entry.Name, entry.SizeMB, entry.Scale)
}

Viewport Loading

// Load charts for viewport (downloads on-demand)
charts, err := mgr.GetChartsForViewport(viewport, 12)
if err != nil {
log.Fatal(err)
}

fmt.Printf("Loaded %d charts\n", len(charts))

// Process features across all charts
for _, chart := range charts {
features := chart.FeaturesInBounds(viewport)
fmt.Printf("%s: %d features\n",
chart.DatasetName(), len(features))
}

Specific Chart Loading

// Load a specific chart by name
chart, err := mgr.GetChart("US5MA22M")
if err != nil {
log.Fatal(err)
}

fmt.Printf("Chart: %s\n", chart.DatasetName())
fmt.Printf("Edition: %s\n", chart.Edition())
fmt.Printf("Features: %d\n", chart.FeatureCount())

Custom Cache Settings

opts := chartmanager.DefaultChartManagerOptions()
opts.CacheSize = 256 * 1024 * 1024 // 256MB memory cache
opts.KeepExtracted = false // Stream from zips (save disk)
opts.MaxCatalogAge = 24 * time.Hour // Refresh catalog daily

mgr, err := chartmanager.NewChartManager(opts)

Catalog Updates

// Force catalog refresh from NOAA
if err := mgr.UpdateCatalog(); err != nil {
log.Printf("Catalog update failed: %v", err)
}

// Check age of cached catalog
catalogPath, _ := chartmanager.DefaultCatalogPath()
info, _ := os.Stat(catalogPath)
age := time.Since(info.ModTime())
fmt.Printf("Catalog age: %v\n", age)

Statistics and Monitoring

stats := mgr.Stats()

fmt.Printf("Catalog Charts: %d\n", stats.CatalogCharts)
fmt.Printf("Cached Charts: %d\n", stats.CachedCharts)
fmt.Printf("Hit Rate: %.1f%%\n", stats.CacheHitRate()*100)
fmt.Printf("Memory: %.1f MB / %.1f MB\n",
float64(stats.CacheMemory)/(1024*1024),
float64(stats.MaxMemory)/(1024*1024))

Performance

Lazy Loading

Charts are downloaded only when needed:

  1. Catalog download: ~90MB, 1-2 seconds (once per week)
  2. Chart download: 0.1-50MB per chart (on first access)
  3. Subsequent access: Instant (loaded from cache)

Memory Cache

The in-memory LRU cache:

  • Default: 512MB
  • Configurable via ChartManagerOptions.CacheSize
  • Automatic eviction of least-recently-used charts
  • Typical chart size: 10-100MB parsed

Disk Space

With KeepExtracted=false (streaming mode):

  • 67% disk space savings
  • Charts read directly from zip files
  • Temporary extraction to /tmp (often tmpfs/RAM)
  • May be faster than extracted mode

With KeepExtracted=true (default):

  • Charts extracted to disk
  • Convenient for external tools
  • Faster for repeated access

HTTP Caching

Catalog downloads use ETag/Last-Modified:

  • 304 Not Modified: No download if catalog unchanged
  • Automatic: No configuration needed
  • Efficient: Avoids 90MB transfer when possible

Error Handling

All functions return descriptive errors:

chart, err := mgr.GetChart("INVALID")
if err != nil {
// Errors include context
log.Printf("Failed to get chart: %v", err)
return
}

Common errors:

  • Chart not found in catalog
  • Network errors during download
  • Disk space issues
  • Invalid catalog format
  • S-57 parsing errors

Integration with s57 Package

The chart manager uses the s57 package for parsing:

// ChartManager returns *s57.Chart
chart, _ := mgr.GetChart("US5MA22M")

// Use s57 API on returned charts
bounds := chart.Bounds() // s57.Bounds
features := chart.Features() // []s57.Feature
viewport := chart.FeaturesInBounds(bounds) // []s57.Feature

See s57 documentation for full chart API.