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.
Bathymetry: Get bathymetry data for depth calculations
Pangaea: Search PANGAEA database for relevant oceanographic datasets
Stations: Interactive station planning (or programmatic configuration)
Process: Enrich configuration with depths/coordinates + validate
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:27Workflow 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