CruisePlan Demo

This notebook demonstrates the complete CruisePlan workflow using Python functions rather than CLI commands. Each section corresponds to a CLI subcommand and shows how to accomplish the same tasks programmatically.

Workflow Overview

The data preparation phase includes steps 1 and 2, the cruise configuration is steps 3 and 4, and the scheduling is step 5.

  1. Bathymetry: Get bathymetry data for depth calculations

  2. Pangaea: Search PANGAEA database for relevant oceanographic datasets

  3. Stations: Interactive station planning (or programmatic configuration)

  4. Process: Enrich configuration with depths/coordinates + validate

  5. Schedule: Generate cruise timeline and outputs

All outputs will be saved to tests_output/demo/ for easy exploration.

Setup and Imports

[1]:
# Core imports
import logging
from pathlib import Path

import xarray as xr
from cruiseplan.utils.global_ports import (
    add_custom_port,
    get_available_ports,
    list_ports_in_region,
)
from cruiseplan.utils.yaml_io import save_yaml

from cruiseplan import bathymetry, pangaea, process, schedule

# Set up output directory
output_dir = Path('../tests_output/demo')
output_dir.mkdir(parents=True, exist_ok=True)

print(f"Demo outputs will be saved to: {output_dir.absolute()}")

# Configure logging to see what's happening
logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s')
Demo outputs will be saved to: /Users/eddifying/Cloudfree/github/cruiseplan/notebooks/../tests_output/demo

Phase 1: Data Preparation

Step 1: Download Bathymetry Data

CLI Equivalent:

cruiseplan bathymetry --bathy-source etopo2022

First, we need bathymetry data for depth calculations and visualization.

[2]:
# Initialize bathymetry manager with ETOPO2022 data
print("📥 Downloading bathymetry data...")
bathy_path = bathymetry(bathy_source="etopo2022")
INFO: =� Downloading etopo2022 bathymetry data to /Users/eddifying/Cloudfree/github/cruiseplan/data/bathymetry
📥 Downloading bathymetry data...
File already exists at /Users/eddifying/Cloudfree/github/cruiseplan/data/bathymetry/ETOPO_2022_v1_60s_N90W180_bed.nc (468.5 MB)

Step 2: Search PANGAEA Database

CLI Equivalent (from root, rather than notebooks/ directory):

cruiseplan pangaea "CTD" --lat 50 70 --lon -60 -30 --limit 5 --output-dir tests_output/demo --output demo`

Search for relevant oceanographic datasets to inform our cruise planning.

[3]:
print("🔍 Searching PANGAEA database and downloading station data...")

# Define search parameters
query = "CTD"  # or "CTD temperature North Atlantic"
lat_bounds = [50, 70]  # min_lat, max_lat
lon_bounds = [-60, -30]  # min_lon, max_lon
limit = 5

print(f"   Query: '{query}'")
print("   Geographic bounds: 50°N-70°N, 60°W-30°W")
print(f"   Limit: {limit} datasets")

# Search and download in one step
try:
    pangaea_stations, pangaea_files = pangaea(
        query_terms=query,
        lat_bounds=lat_bounds,
        lon_bounds=lon_bounds,
        max_results=limit,
        output_dir=str(output_dir),
        output="demo"
    )

    if pangaea_files:
        print("✅ PANGAEA processing completed!")
        for files in pangaea_files:
            print(f"   📄 {files!s}")
    else:
        print("❌ No datasets found or processing failed")

except Exception as e:
    print(f"❌ PANGAEA processing failed: {e}")
🔍 Searching PANGAEA database and downloading station data...
   Query: 'CTD'
   Geographic bounds: 50°N-70°N, 60°W-30°W
   Limit: 5 datasets
INFO: 🔍 Searching PANGAEA for: 'CTD'
INFO: 📍 Geographic bounds: lat [50, 70], lon [-60, -30]
INFO: 🔍 Searching PANGAEA for: 'CTD'
INFO: 📍 Geographic bounds: 50.00°N to 70.00°N, 60.00°W to 30.00°W
INFO: Searching Pangaea: 'CTD' (Limit: 5)
INFO: Search found 1823 total matches. Retrieving first 5...
INFO: ✓ Found 5 datasets with valid DOIs
INFO: ✅ Found 5 datasets
INFO: 📂 DOI file: /Users/eddifying/Cloudfree/github/cruiseplan/tests_output/demo/demo_dois.txt
INFO: 📂 Stations file: /Users/eddifying/Cloudfree/github/cruiseplan/tests_output/demo/demo_stations.pkl
INFO: ⚙️ Processing 5 DOIs...
INFO: 🕐 Rate limit: 1.0 requests/second
INFO: Starting fetch of 5 PANGAEA datasets
INFO: 🕐 Rate limit: 1.0 requests/second
INFO: [1/5] Fetching 10.1594/PANGAEA.755512
INFO: [1/5] ✓ Retrieved dataset
INFO: [2/5] Fetching 10.1594/PANGAEA.604878
INFO: [2/5] ✓ Retrieved dataset
INFO: [3/5] Fetching 10.1594/PANGAEA.604842
INFO: [3/5] ✓ Retrieved dataset
INFO: [4/5] Fetching 10.1594/PANGAEA.604887
INFO: [4/5] ✓ Retrieved dataset
INFO: [5/5] Fetching 10.1594/PANGAEA.604857
INFO: [5/5] ✓ Retrieved dataset
INFO: Merged 5 datasets into 2 campaigns
INFO: Completed: 2 datasets from 5 DOIs
INFO: Saved 2 datasets to: /Users/eddifying/Cloudfree/github/cruiseplan/tests_output/demo/demo_stations.pkl
INFO: Summary:
INFO:   - 5 datasets
INFO:   - 2 unique campaigns
INFO:   - 14 total events/stations
INFO: ✅ PANGAEA processing completed successfully!
INFO: 🚀 Next step: cruiseplan stations -p /Users/eddifying/Cloudfree/github/cruiseplan/tests_output/demo/demo_stations.pkl
✅ PANGAEA processing completed!
   📄 /Users/eddifying/Cloudfree/github/cruiseplan/tests_output/demo/demo_dois.txt
   📄 /Users/eddifying/Cloudfree/github/cruiseplan/tests_output/demo/demo_stations.pkl

Phase 2: Cruise configuration

Step 3: Create Station Configuration

CLI Equivalent:

cruiseplan stations --pangaea-file demo_stations.pkl --lat 50 70 --lon -60 -30`

BUT for this demo, we’ll create a programmatic station configuration rather than using the interactive interface. Maybe we’ll make an interactive demo to show the other options, but the interactive part runs nicely from the command line.

[ ]:
print("🗺️  Creating station configuration...")

# Create a sample cruise configuration
cruise_config = {
    'cruise_name': 'Demo North Atlantic Survey 2025',
    'default_vessel_speed': 10.0,
    'turnaround_time': 30.0,
    'ctd_descent_rate': 1.0,
    'ctd_ascent_rate': 1.0,
    'calculate_transfer_between_sections': True,
    'calculate_depth_via_bathymetry': True,
    'start_date': '2025-06-01T00:00:00Z',

    # Define points along a transect
    'points': [
        {
            'name': 'STN_001',
            'latitude': 55.0,
            'longitude': -50.0,
            'operation_type': 'CTD',
            'action': 'profile',
            'comment': 'Continental shelf station'
        },
        {
            'name': 'STN_002',
            'latitude': 57.0,
            'longitude': -45.0,
            'operation_type': 'CTD',
            'action': 'profile',
            'comment': 'Slope station'
        },
        {
            'name': 'STN_003',
            'latitude': 59.0,
            'longitude': -40.0,
            'operation_type': 'CTD',
            'action': 'profile',
            'comment': 'Deep water station'
        },
        {
            'name': 'STN_004',
            'latitude': 61.0,
            'longitude': -35.0,
            'operation_type': 'water_sampling',
            'action': 'sampling',
            'duration': 180.0,
            'comment': 'Water sampling station'
        },
        {
            'name': 'MOOR_001',
            'latitude': 60.5,
            'longitude': -38.0,
            'operation_type': 'mooring',
            'action': 'deployment',
            'duration': 240.0,
            'comment': 'Mooring deployment'
        },
        {
            'name': 'STN_005',
            'latitude': 63.0,
            'longitude': -30.0,
            'operation_type': 'CTD',
            'action': 'profile',
            'comment': 'Northern end station'
        },
        {
            'name': 'STN_006',
            'latitude': 58.0,
            'longitude': -42.0,
            'operation_type': 'CTD',
            'action': 'profile',
            'comment': 'Return transect station'
        }
    ],

    # Add scientific lines
    'lines': [
        {
            'name': 'Demo_ADCP_Survey',
            'operation_type': 'underway',
            'action': 'ADCP',
            'vessel_speed': 5.0,
            'route': [
                {'latitude': 56.0, 'longitude': -48.0},
                {'latitude': 56.0, 'longitude': -38.0}
            ],
            'comment': 'Zonal ADCP survey transect'
        }
    ],

    # Define execution order with leg-based port definitions
    'legs': [
        {
            'name': 'Demo_Survey',
            'departure_port': 'port_st_johns',
            'arrival_port': 'port_st_johns',
            'first_waypoint': 'STN_001',
            'last_waypoint': 'STN_006',
            'activities': ['STN_001', 'STN_002', 'STN_003', 'Demo_ADCP_Survey', 'STN_004', 'MOOR_001', 'STN_005', 'STN_006']
        }
    ]
}

# Save configuration
config_file = output_dir / "demo_cruise.yaml"
save_yaml(cruise_config, config_file)

print("✅ Station configuration created")
print(f"   Configuration saved to: {config_file}")
print(f"   Points: {len(cruise_config['points'])}")
print(f"   Lines: {len(cruise_config['lines'])}")
print(f"   Legs: {len(cruise_config['legs'])}")

Step 3.5: Manually edit configuration

The cruise configuration in stations.yaml will have some default values that need to be manually updated. These include the departure_port and arrival_port within the cruise leg definition. Default values are called “port_update”, but can be replaced using values from the catalog of ports.

legs:
  - name: Interactive_Survey
    departure_port: port_update
    arrival_port: port_update
    first_waypoint: STN_001
    last_waypoint: STN_002
    strategy: sequential
    activities:
      - STN_001
      - STN_002
      - Transit_01
      - Area_01
[5]:
# Using cruiseplan.utils.global_ports with get_available_ports() and list_ports_in_region()

ports = get_available_ports()

# Display them nicely
for port_id, description in ports.items():
   print(f"{port_id}: {description}")

# Or get ports in a specific region
north_atlantic_ports = list_ports_in_region(
      min_lat=50.0, max_lat=70.0,
      min_lon=-30.0, max_lon=20.0
  )

for port_id, port_name in north_atlantic_ports.items():
    print(f"{port_id}: {port_name}")

# If you want to add a custom port for your project
add_custom_port("port_my_station", {
    "name": "My Research Station",
    "display_name": "My Research Station, Location",
    "latitude": 60.0,
    "longitude": -20.0,
    "timezone": "GMT+0",
    "description": "Custom research station for this cruise"
})
port_reykjavik: Iceland capital, subpolar/Nordic Seas
port_nuuk: Greenland capital, Arctic research gateway
port_tromso: Northern Norway, Arctic gateway
port_trondheim: Central Norway, Norwegian Sea operations
port_bergen: Western Norway, Nordic Seas
port_southampton: UK south coast, Atlantic access
port_bremerhaven: Germany, Arctic and Atlantic operations
port_hamburg: Germany, North Sea and Baltic access
port_emden: Germany, North Sea operations
port_rostock: Germany, Baltic Sea operations
port_kiel: Germany, Baltic Sea research hub
port_brest: France, Atlantic operations
port_nice: France, Mediterranean research
port_vigo: Spain northwest, Atlantic margin research
port_cadiz: Spain southwest, Atlantic and Mediterranean
port_malaga: Spain south coast, Mediterranean research
port_heraklion: Crete, Eastern Mediterranean research
port_catania: Sicily, Mediterranean research
port_limassol: Cyprus, Eastern Mediterranean operations
port_las_palmas: Canary Islands, subtropical Atlantic research
port_ponta_delgada: Azores, mid-Atlantic research hub
port_funchal: Madeira, subtropical Atlantic operations
port_mindelo: Cape Verde, tropical Atlantic research
port_walvis_bay: Namibia, Benguela upwelling system
port_durban: South Africa, Indian Ocean operations
port_halifax: Nova Scotia, North Atlantic research hub
port_st_johns: Newfoundland, Labrador Sea operations
port_vancouver: British Columbia, North Pacific research
port_woods_hole: Massachusetts, major oceanographic center
port_san_diego: California, Pacific research operations
port_astoria: Oregon, Pacific Northwest research
port_honolulu: Hawaii, central Pacific research hub
port_ensenada: Baja California, eastern Pacific research
port_balboa: Panama, Pacific-Caribbean operations
port_bridgetown: Barbados, Caribbean research hub
port_rio_de_janeiro: Brazil, South Atlantic research
port_fortaleza: Brazil northeast, equatorial Atlantic
port_belem: Brazil, Amazon outflow studies
port_recife: Brazil, tropical Atlantic research
port_antofagasta: Chile, Humboldt current research
port_port_louis_mauritius: Mauritius, western Indian Ocean research
port_la_reunion: Reunion, southwestern Indian Ocean
port_port_louis_seychelles: Seychelles, equatorial Indian Ocean
port_colombo: Sri Lanka, northern Indian Ocean research
port_singapore: Singapore, Southeast Asian research hub
port_yokohama: Japan, western Pacific research
port_fremantle: Western Australia, Indian Ocean research
port_wellington: New Zealand, Southern Ocean research
port_auckland: New Zealand, Southwest Pacific research
port_papeete: French Polynesia, central Pacific research
port_update: Default port for station picker - update with actual ports
port_reykjavik: Reykjavik
port_tromso: Tromsø
port_trondheim: Trondheim
port_bergen: Bergen
port_southampton: Southampton
port_bremerhaven: Bremerhaven
port_hamburg: Hamburg
port_emden: Emden
port_rostock: Rostock
port_kiel: Kiel
port_update: Reykjavik (DEFAULT in stations.yaml)

Step 4: Process Configuration

CLI Equivalent: cruiseplan enrich -c demo_cruise.yaml --add-depths --add-coords --expand-sections

Add computed data like depths, formatted coordinates, and expand CTD sections.

[6]:
print("🔧 Processing configuration (enrich + validate + map)...")
config_file = output_dir / "demo_cruise.yaml"

try:
    cruise_config, generated_config_files = process(
        config_file=config_file,
        output_dir=str(output_dir),
        output="demo_cruise",
        format="png",
        bathy_source="etopo2022",
        bathy_dir="../data/bathymetry",  # Go up to project root, then into data
        bathy_stride=10,
        figsize=[12, 8],
        depth_check=True,
        tolerance=10
    )

    if cruise_config:
        print("✅ Processing completed successfully!")
        print("   - Configuration enriched")
        print("   - Validation passed")
        for file in generated_config_files:
            print(f"   📄 {file!s}")
    else:
        print("❌ Processing failed")

except Exception as e:
    print(f"❌ Processing failed: {e}")
    import traceback
    traceback.print_exc()

INFO: 🔧 Enriching cruise configuration...
INFO: 🔧 Enriching /Users/eddifying/Cloudfree/github/cruiseplan/tests_output/demo/demo_cruise.yaml
INFO: 📁 Output will be saved to: /Users/eddifying/Cloudfree/github/cruiseplan/tests_output/demo/demo_cruise_enriched.yaml
WARNING: ⚠️ Added missing field: default_distance_between_stations = 15.0 km
INFO: ℹ️ Missing required fields added with defaults. Update these values as needed.
INFO: Saved configuration to: /var/folders/t1/z5bp59k95119nw35yqv699t40000gn/T/tmpmnmvlv2r.yaml
INFO: ✅ Loaded bathymetry from /Users/eddifying/Cloudfree/github/cruiseplan/data/bathymetry/ETOPO_2022_v1_60s_N90W180_bed.nc
🔧 Processing configuration (enrich + validate + map)...
INFO: Saved configuration to: /Users/eddifying/Cloudfree/github/cruiseplan/tests_output/demo/demo_cruise_enriched.yaml
INFO: ✅ Validating cruise configuration...
INFO:  Validating /Users/eddifying/Cloudfree/github/cruiseplan/tests_output/demo/demo_cruise_enriched.yaml
INFO: ✅ Loaded bathymetry from /Users/eddifying/Cloudfree/github/cruiseplan/data/bathymetry/ETOPO_2022_v1_60s_N90W180_bed.nc
INFO: ✅ Validation passed
INFO: 🗺️ Generating cruise maps...
INFO: Display bounds: 42.6°-68.0°N, -57.7°--25.0°E
INFO: Loading bathymetry for region: 39.6°-71.0°N, -60.7°--22.0°E
INFO: ✅ Loaded bathymetry from /Users/eddifying/Cloudfree/github/cruiseplan/data/bathymetry/ETOPO_2022_v1_60s_N90W180_bed.nc
INFO: Added bathymetry contours covering full region
INFO: Map displayed with 9 points, 1 lines, 0 areas
INFO: Map saved to /Users/eddifying/Cloudfree/github/cruiseplan/tests_output/demo/demo_cruise_map.png
INFO: ✅ Processing workflow completed successfully!
✅ Processing completed successfully!
   - Configuration enriched
   - Validation passed
   📄 /Users/eddifying/Cloudfree/github/cruiseplan/tests_output/demo/demo_cruise_enriched.yaml
   📄 /Users/eddifying/Cloudfree/github/cruiseplan/tests_output/demo/demo_cruise_map.png

Phase 3: Cruise scheduling + outputs

Step 5: Generate Schedule

CLI Equivalent: cruiseplan schedule -c demo_cruise_enriched.yaml -o tests_output/demo/ --format html,csv,netcdf --derive-netcdf

Generate the complete cruise timeline and output files.

[7]:
for files in generated_config_files:
    if str(files).endswith('enriched.yaml'):
        enriched_config_file = output_dir / "demo_cruise_enriched.yaml"


print("📅 Generating cruise schedule...")

try:
    # Generate the schedule with multiple output formats using new API
    timeline, generated_files = schedule(
        config_file=enriched_config_file,
        output_dir="../tests_output/demo",
        output="demo_cruise",
        format="html,csv,netcdf",
        leg=None,
        derive_netcdf=True
    )

    print("✅ Schedule generated successfully!")
    if timeline:
        print(f"   Timeline object created with {len(timeline)} activities")
        print("   Output files in: ../tests_output/demo/")
        print("   Generated formats: HTML, CSV, NetCDF")
        for file in generated_files:
            print(f"   - {file}")

    else:
        print("⚠️ No timeline generated")

except Exception as e:
    print(f"❌ Schedule generation failed: {e}")
    # Optionally show the full traceback for debugging
    import traceback
    traceback.print_exc()
INFO: 📅 Generating schedule from /Users/eddifying/Cloudfree/github/cruiseplan/tests_output/demo/demo_cruise_enriched.yaml
INFO: Processing leg 'Demo_Survey': Leg 'Demo_Survey': St. John's (round trip), 0 operations, 0 clusters
INFO: Generated maritime timeline with 17 activities
INFO: 🌐 HTML Generator: Starting generation of /Users/eddifying/Cloudfree/github/cruiseplan/tests_output/demo/demo_cruise_schedule.html
INFO:    Timeline contains 17 activities
INFO: 🔍 HTML Generator: Processing 17 timeline activities
INFO: ✅ Generated HTML schedule: /Users/eddifying/Cloudfree/github/cruiseplan/tests_output/demo/demo_cruise_schedule.html
INFO: 📊 CSV Generator: Starting generation of /Users/eddifying/Cloudfree/github/cruiseplan/tests_output/demo/demo_cruise_schedule.csv
INFO:    Timeline contains 17 activities
INFO: ✅ Generated CSV schedule: /Users/eddifying/Cloudfree/github/cruiseplan/tests_output/demo/demo_cruise_schedule.csv
INFO: 📄 NetCDF Generator: Starting generation of /Users/eddifying/Cloudfree/github/cruiseplan/tests_output/demo/demo_cruise_schedule.nc
INFO:    Timeline contains 17 activities
INFO: Generating ship schedule NetCDF: /Users/eddifying/Cloudfree/github/cruiseplan/tests_output/demo/demo_cruise_schedule.nc
INFO: Ship schedule NetCDF written to: /Users/eddifying/Cloudfree/github/cruiseplan/tests_output/demo/demo_cruise_schedule.nc
INFO: ✅ Generated NetCDF schedule: /Users/eddifying/Cloudfree/github/cruiseplan/tests_output/demo/demo_cruise_schedule.nc
INFO: 📅 Schedule generation complete! Generated 3 files
📅 Generating cruise schedule...
✅ Schedule generated successfully!
   Timeline object created with 17 activities
   Output files in: ../tests_output/demo/
   Generated formats: HTML, CSV, NetCDF
   - /Users/eddifying/Cloudfree/github/cruiseplan/tests_output/demo/demo_cruise_schedule.html
   - /Users/eddifying/Cloudfree/github/cruiseplan/tests_output/demo/demo_cruise_schedule.csv
   - /Users/eddifying/Cloudfree/github/cruiseplan/tests_output/demo/demo_cruise_schedule.nc
[8]:
# Find the *.nc file in generated_files list of paths
schedule_file = None
for file in generated_files:
    if str(file).endswith('.nc'):
        schedule_file = file
        break
ds = xr.open_dataset(schedule_file)
ds
[8]:
<xarray.Dataset> Size: 6kB
Dimensions:         (obs: 17)
Coordinates:
    time            (obs) datetime64[ns] 136B ...
    longitude       (obs) float32 68B ...
    latitude        (obs) float32 68B ...
Dimensions without coordinates: obs
Data variables:
    name            (obs) <U35 2kB ...
    category        (obs) <U15 1kB ...
    type            (obs) <U10 680B ...
    action          (obs) <U10 680B ...
    comment         (obs) <U1 68B ...
    leg_assignment  (obs) <U11 748B ...
    duration        (obs) float32 68B ...
    vessel_speed    (obs) float32 68B ...
Attributes:
    featureType:          trajectory
    title:                Ship Schedule: Demo North Atlantic Survey 2025
    institution:          Generated by CruisePlan software
    source:               Scheduler computation from YAML configuration
    Conventions:          CF-1.8
    cruise_name:          Demo North Atlantic Survey 2025
    total_duration_days:  6.039044819772243e-10
    creation_date:        2025-12-22T11:16:27

Workflow Summary

Let’s review what we’ve accomplished and check our output files.

[9]:
all_files = pangaea_files + [config_file] + generated_config_files + generated_files

print("✅ CruisePlan Demo Complete!")
print(f"📁 Generated files: {len(all_files)} files in {Path(output_dir).name}/")
for file in all_files:
    print(f"   📄 {file.name}")

✅ CruisePlan Demo Complete!
📁 Generated files: 8 files in demo/
   📄 demo_dois.txt
   📄 demo_stations.pkl
   📄 demo_cruise.yaml
   📄 demo_cruise_enriched.yaml
   📄 demo_cruise_map.png
   📄 demo_cruise_schedule.html
   📄 demo_cruise_schedule.csv
   📄 demo_cruise_schedule.nc