Maritime Data Product Tutorial
Introduction
This tutorial demonstrates how to build a comprehensive maritime data product using DataCardsā modular workflow approach. The system processes ship data, environmental conditions, and anchoring requirements to provide intelligent anchorage recommendations.
The data product consists of six interconnected modules, each handling specific aspects of maritime data processing:
- Schiff (Ship) - Central data hub and ship database
- Windkraftmodell (Wind Power Model) - Wind strength analysis and power calculations
- Wasserkraftmodell (Hydro Power Model) - Current strength, tides, and hydro power analysis
- Datenprodukt (Data Product) - Core processing with original vs reference path comparison
- Ankermodell (Anchor Model) - Anchor positioning and holding power calculations
- Wassertiefe (Water Depth) - Depth analysis with real-time gauge data integration
Each module publishes specific variables that connect notebooks, enabling a flexible, modular architecture where components can be developed and tested independently.
Architecture Overview
The maritime data product follows a data flow architecture where:
- Input data flows from ship systems and environmental sources
- Processing modules transform and analyze the data
- Published variables enable notebook connections and data sharing
- Feedback loops allow real-time adjustments based on current conditions
This modular approach ensures:
- Scalability: Each module can be scaled independently
- Maintainability: Clear separation of concerns
- Testability: Individual modules can be tested in isolation
- Flexibility: Easy to add new data sources or modify existing logic
Module 1: Schiffsdaten (Ship Data)
Purpose
Simple input module for ship data using DataCards slider cards. This module provides the basic ship information needed by other modules, matching the structure shown in the screenshot.
DataCards Input Cards
The Ship module contains three essential data elements as DataCards input cards:
- Positionsdaten Schiff (LONG, LAT) - Ship position data with longitude and latitude coordinates
- Stammdaten Schiff - Ship master data and core vessel information
- Peilung (K_rw) [grad oder strich] - Bearing data in degrees or strich (navigation units)
Positionsdaten (Position Data)
# Ship Position - Latitude
# First publish the variable
datacards.publish.variable('latitude_input', 55.0)
# Then publish the card that consumes it
datacards.publish.card(
type='floatSlider',
label='Latitude',
unit='°N',
min=50.0,
max=60.0,
step=0.01,
variable_key='latitude_input',
logic_view_size=(2,1),
layout=[{"size": (3,2), "position": (0,0), "deck": "default-deck"}]
)
# Ship Position - Longitude
# First publish the variable
datacards.publish.variable('longitude_input', 10.0)
# Then publish the card that consumes it
datacards.publish.card(
type='floatSlider',
label='Longitude',
unit='°E',
min=5.0,
max=15.0,
step=0.01,
variable_key='longitude_input',
logic_view_size=(2,1),
layout=[{"size": (3,2), "position": (3,0), "deck": "default-deck"}]
)Stammdaten Schiff (Ship Master Data)
Vessel Name
# First publish the variable
datacards.publish.variable('vessel_name_input', 'MV DataCard Explorer')
# Then publish the card that consumes it
datacards.publish.card(
type='text',
label='Vessel Name',
variable_key='vessel_name_input',
logic_view_size=(2,1),
layout=[{"size": (3,2), "position": (0,2), "deck": "default-deck"}]
)Gross Tonnage
# First publish the variable
datacards.publish.variable('gross_tonnage_input', 5000)
# Then publish the card that consumes it
datacards.publish.card(
type='intSlider',
label='Gross Tonnage',
unit='GT',
min=100,
max=10000,
step=50,
variable_key='gross_tonnage_input',
logic_view_size=(2,1),
layout=[{"size": (3,2), "position": (3,2), "deck": "default-deck"}]
)Vessel Length
# First publish the variable
datacards.publish.variable('vessel_length_input', 120.0)
# Then publish the card that consumes it
datacards.publish.card(
type='floatSlider',
label='Vessel Length',
unit='m',
min=50.0,
max=200.0,
step=1.0,
variable_key='vessel_length_input',
logic_view_size=(2,1),
layout=[{"size": (3,2), "position": (0,4), "deck": "default-deck"}]
)Vessel Width
# First publish the variable
datacards.publish.variable('vessel_width_input', 18.0)
# Then publish the card that consumes it
datacards.publish.card(
type='floatSlider',
label='Vessel Width',
unit='m',
min=10.0,
max=50.0,
step=0.5,
variable_key='vessel_width_input',
logic_view_size=(2,1),
layout=[{"size": (3,2), "position": (3,4), "deck": "default-deck"}]
)Draft
# First publish the variable
datacards.publish.variable('draft_input', 6.5)
# Then publish the card that consumes it
datacards.publish.card(
type='floatSlider',
label='Draft',
unit='m',
min=2.0,
max=15.0,
step=0.1,
variable_key='draft_input',
logic_view_size=(2,1),
layout=[{"size": (3,2), "position": (0,6), "deck": "default-deck"}]
)Peilung (Bearing)
Bearing
# First publish the variable
datacards.publish.variable('bearing_input', 180.0)
# Then publish the card that consumes it
datacards.publish.card(
type='floatSlider',
label='Bearing',
unit='°',
min=0.0,
max=360.0,
step=1.0,
variable_key='bearing_input',
logic_view_size=(2,1),
layout=[{"size": (3,2), "position": (3,6), "deck": "default-deck"}]
)Bearing Units Toggle
# First publish the variable
datacards.publish.variable('bearing_units_input', False)
# Then publish the card that consumes it
datacards.publish.card(
type='toggle',
label='Units (Degrees/Strich)',
variable_key='bearing_units_input',
logic_view_size=(2,1),
layout=[{"size": (3,2), "position": (6,6), "deck": "default-deck"}]
)Additional ship data needed for Windkraftmodell
Lateralplan (Lateral Plan Area)
# First publish the variable
datacards.publish.variable('lateralplan_input', 50.0)
# Then publish the card that consumes it
datacards.publish.card(
type='floatSlider',
label='Lateral Plan Area',
unit='m²',
min=10.0,
max=200.0,
step=1.0,
variable_key='lateralplan_input',
logic_view_size=(2,1),
layout=[{"size": (3,2), "position": (0,8), "deck": "default-deck"}]
)Bootstyp (Boat Type)
# First publish the variable
datacards.publish.variable('bootstyp_input', 'motor')
# Then publish the card that consumes it
datacards.publish.card(
type='combobox',
label='Boat Type',
options=['segel', 'motor'],
variable_key='bootstyp_input',
logic_view_size=(2,1),
layout=[{"size": (3,2), "position": (3,8), "deck": "default-deck"}]
)Masthƶhe (Mast Height)
# First publish the variable
datacards.publish.variable('masthoehe_input', 15.0)
# Then publish the card that consumes it
datacards.publish.card(
type='floatSlider',
label='Mast Height',
unit='m',
min=5.0,
max=50.0,
step=0.5,
variable_key='masthoehe_input',
logic_view_size=(2,1),
layout=[{"size": (3,2), "position": (6,8), "deck": "default-deck"}]
)Published Variables
| Variable Name | Type | Description | Source |
|---|---|---|---|
latitude_input | float | Ship latitude position | Slider input |
longitude_input | float | Ship longitude position | Slider input |
vessel_name_input | string | Vessel name | Text input |
gross_tonnage_input | int | Gross tonnage | Slider input |
vessel_length_input | float | Vessel length in meters | Slider input |
vessel_width_input | float | Vessel width in meters | Slider input |
draft_input | float | Vessel draft in meters | Slider input |
bearing_input | float | Bearing in degrees | Slider input |
bearing_units_input | bool | Units toggle (degrees/strich) | Toggle input |
lateralplan_input | float | Lateral plan area in m² | Slider input |
bootstyp_input | string | Boat type (segel/motor) | Combobox input |
masthoehe_input | float | Mast height in meters | Slider input |
Module 2: Windkraftmodell (Wind Power Model)
Purpose
Calculates wind force and wind attack on the ship based on wind speed, ship dimensions, and orientation. The model implements the proper maritime wind calculations including land cover corrections, height adjustments, and presented attack surface calculations.
DataCards Input Cards
Abdeckung (Coverage) Slider
# Abdeckung (Coverage) - Wind Coverage Factor
# First publish the variable
datacards.publish.variable('abdeckung_input', 1.0)
# Then publish the card that consumes it
datacards.publish.card(
type='floatSlider',
label='Wind Coverage Factor',
unit='Factor',
min=0.0,
max=1.0,
step=0.01,
variable_key='abdeckung_input',
logic_view_size=(2,1),
layout=[{"size": (3,2), "position": (0,0), "deck": "default-deck"}]
)Published Variables
| Variable Name | Type | Description | Source |
|---|---|---|---|
abdeckung_input | float | Wind coverage factor (0.0-1.0) | Slider input |
windstaerke | Series | Wind strength (Beaufort scale or m/s) | Meteorological data, ship sensors |
windkraft | Series | Total wind force on the ship (kN) | Calculated from wind strength, ship profile |
windengriff | Series | Wind attack area / effective windage area (m²) | Ship master data, design parameters |
Implementation
# Wind Power Model Notebook
import pandas as pd
import numpy as np
# Consume ship data from Schiffsdaten module
lateralplan = dc.consume.variable.lateralplan_input()
bootstyp = dc.consume.variable.bootstyp_input()
masthoehe = dc.consume.variable.masthoehe_input()
# Consume wind coverage factor
abdeckung_factor = dc.consume.variable.abdeckung_input()
# Create placeholder data (since these variables don't exist yet)
# In a real implementation, these would be consumed from other modules
wind_data = pd.DataFrame({
'wind_speed_10m': np.random.uniform(0, 25, 100),
'wind_direction_10m': np.random.uniform(0, 360, 100),
'wind_speed_50m': np.random.uniform(0, 30, 100),
'wind_direction_50m': np.random.uniform(0, 360, 100),
'gust_speed': np.random.uniform(0, 35, 100)
})
ship_data = pd.DataFrame({
'latitude': np.random.uniform(53.5, 54.5, 100),
'longitude': np.random.uniform(10.0, 11.0, 100),
'heading': np.random.uniform(0, 360, 100),
'speed': np.random.uniform(0, 20, 100)
})
stammdaten = pd.DataFrame([{
'vessel_name': 'MV DataCard Explorer',
'beam': 18.0, # BĆA
'length_overall': 120.0, # LĆA
'gross_tonnage': 5000,
'draft_max': 6.5
}])
def apply_abdeckung_correction(wind_speed, abdeckung_factor):
"""
Apply coverage correction to wind speed based on land shelter effects.
Based on the technical diagrams:
- 1.0 = No coverage (open sea)
- 0.7-0.9 = Steep coast coverage
- 0.5-0.8 = Tree coverage
"""
corrected_wind_speed = wind_speed * abdeckung_factor
return corrected_wind_speed
def apply_height_correction(wind_speed, reference_height=12, target_height=3):
"""
Apply height correction using Power Law (Potenzgesetz).
Formula: v(z) = v(z_ref) * (z / z_ref)^α
Where α = 0.11 for open sea (roughness factor)
"""
alpha = 0.11 # Roughness factor for open sea
if reference_height <= 0 or target_height <= 0:
return wind_speed
height_ratio = target_height / reference_height
corrected_wind_speed = wind_speed * (height_ratio ** alpha)
return corrected_wind_speed
def calculate_wind_pressure(wind_speed):
"""
Calculate wind pressure using the formula: p = ½ * Ļ * v²
For standard conditions (sea level, 15°C):
Ļ = 1.225 kg/m³
p = 0.6125 * v² (with v in m/s, p in N/m²)
"""
# Simplified formula for standard conditions
wind_pressure = 0.6125 * (wind_speed ** 2)
return wind_pressure
def calculate_presented_width(boat_beam, boat_length, wind_angle_deg):
"""
Calculate presented width using trigonometric function.
Formula: W[Ļ] = | B * cos(Ļ) + L * sin(Ļ) |
Where:
- B = BĆA (Breite über Alles - Overall Beam)
- L = LĆA (LƤnge über Alles - Overall Length)
- Ļ = angle between bow direction and wind direction
"""
# Convert angle to radians
wind_angle_rad = np.radians(wind_angle_deg)
# Calculate presented width
presented_width = abs(
boat_beam * np.cos(wind_angle_rad) +
boat_length * np.sin(wind_angle_rad)
)
return presented_width
def calculate_effective_height(cabin_height, mast_height):
"""
Calculate effective height for wind attack.
Formula: H = cabin_height + 0.4 * mast_height
This is an empirical value for wind attack on rigging.
"""
effective_height = cabin_height + 0.4 * mast_height
return effective_height
def calculate_wind_force(wind_pressure, presented_width, effective_height):
"""
Calculate total wind force on the ship.
Formula: Windkraft = Winddruck * AngriffsflƤche
Where: AngriffsflƤche = W * H (presented width * effective height)
"""
attack_surface = presented_width * effective_height
wind_force = wind_pressure * attack_surface
return wind_force
def calculate_wind_angle(ship_heading, wind_direction):
"""
Calculate angle between ship bow direction and wind direction.
Ļ = 0° means wind from front
Ļ = 90° means wind from side
"""
# Calculate relative angle
angle_diff = wind_direction - ship_heading
# Normalize to 0-360 degrees
angle_diff = angle_diff % 360
# Convert to -180 to +180 range
if angle_diff > 180:
angle_diff -= 360
return abs(angle_diff) # Return absolute value
# Process wind data through the complete model
windstaerke, windkraft, windengriff = process_wind_data(
wind_data, ship_data, stammdaten,
lateralplan, bootstyp, masthoehe, abdeckung_factor
)
# Publish results
dc.publish.variable('windstaerke', windstaerke)
dc.publish.variable('windkraft', windkraft)
dc.publish.variable('windengriff', windengriff)Module 3: Wasserkraftmodell (Hydro Power Model)
Purpose
Calculates water current forces and hydro power effects on ship operations using proper maritime fluid dynamics. The model implements current speed analysis, dynamic pressure calculations, and underwater attack surface calculations for accurate hydro power modeling.
DataCards Input Cards
Strƶmung (Current) Slider
# Strƶmung (Current) - Current Speed
# First publish the variable
dc.publish.variable('stroemung_input', 2.0)
# Then publish the card that consumes it
dc.publish.card(
type='floatSlider',
label='Current Speed',
unit='m/s',
min=0.0,
max=5.0,
step=0.1,
variable_key='stroemung_input',
logic_view_size=(2,1),
layout=[{"size": (3,2), "position": (0,0), "deck": "default-deck"}]
)Gezeiten (Tidal) Sliders
Tidal Height
# First publish the variable
dc.publish.variable('gezeiten_height_input', 1.2)
# Then publish the card that consumes it
dc.publish.card(
type='floatSlider',
label='Tidal Height',
unit='m',
min=0.0,
max=3.0,
step=0.1,
variable_key='gezeiten_height_input',
logic_view_size=(2,1),
layout=[{"size": (3,2), "position": (3,0), "deck": "default-deck"}]
)Tidal Velocity
# First publish the variable
dc.publish.variable('gezeiten_velocity_input', 0.5)
# Then publish the card that consumes it
dc.publish.card(
type='floatSlider',
label='Tidal Velocity',
unit='m/s',
min=0.0,
max=2.0,
step=0.05,
variable_key='gezeiten_velocity_input',
logic_view_size=(2,1),
layout=[{"size": (3,2), "position": (6,0), "deck": "default-deck"}]
)Published Variables
| Variable Name | Type | Description | Source |
|---|---|---|---|
stroemung_input | float | Current speed (m/s) | Slider input |
gezeiten_height_input | float | Tidal height (m) | Slider input |
gezeiten_velocity_input | float | Tidal velocity (m/s) | Slider input |
stromstaerke | Series | Current speed and direction (m/s, degrees) | Current data from Ship module |
gezeiten | Series | Tidal height and velocity (m, m/s) | Slider inputs |
wasserkraft | Series | Calculated hydro power force (N) | Dynamic pressure and attack surface calculations |
stroemungsangriff | Series | Current attack force on ship (N) | Final current attack force |
Implementation
# Hydro Power Model Notebook
import pandas as pd
import numpy as np
import datacards as dc
# Hydro calculation functions
def calculate_dynamic_pressure(current_speed, water_density=1005):
"""
Calculate dynamic pressure using the formula: q = ½ * Ļ * v²
Args:
current_speed: Current speed in m/s
water_density: Water density in kg/m³ (default: 1005 for Baltic Sea)
Returns:
Dynamic pressure in Pa
"""
dynamic_pressure = 0.5 * water_density * (current_speed ** 2)
return dynamic_pressure
def calculate_underwater_attack_surface(boat_beam, boat_length, current_angle_deg, draft):
"""
Calculate underwater attack surface using presented width formula.
Based on maritime engineering principles:
- Presented width = beam * sin(current_angle) + length * cos(current_angle)
- Underwater area = presented width * draft
Args:
boat_beam: Ship beam in meters (BĆA)
boat_length: Ship length in meters (LĆA)
current_angle_deg: Angle between current and ship heading in degrees
draft: Ship draft in meters
Returns:
Underwater attack surface in m²
"""
current_angle_rad = np.radians(current_angle_deg)
# Calculate presented width
presented_width = boat_beam * np.sin(current_angle_rad) + boat_length * np.cos(current_angle_rad)
# Calculate underwater attack surface
underwater_attack_surface = presented_width * draft
return underwater_attack_surface
def calculate_current_force(dynamic_pressure, attack_surface):
"""
Calculate current force on ship.
Args:
dynamic_pressure: Dynamic pressure in Pa
attack_surface: Underwater attack surface in m²
Returns:
Current force in N
"""
current_force = dynamic_pressure * attack_surface
return current_force
def calculate_current_angle(ship_heading, current_direction):
"""
Calculate angle between ship bow direction and current direction.
Args:
ship_heading: Ship heading in degrees (0-360)
current_direction: Current direction in degrees (0-360)
Returns:
Angle difference in degrees (0-180)
"""
angle_diff = abs(ship_heading - current_direction)
# Normalize to 0-180 degrees
if angle_diff > 180:
angle_diff -= 360
return abs(angle_diff) # Return absolute value
def get_baltic_sea_density():
"""Get water density for Baltic Sea conditions"""
return 1005 # kg/m³
def calculate_tidal_effects(tidal_data, timestamp, current_data_index=0):
"""Calculate tidal height and velocity effects"""
if len(tidal_data) > 0 and 'tidal_component' in tidal_data.columns:
# Use actual tidal data if available
tidal_height = tidal_data['tidal_component'].iloc[current_data_index % len(tidal_data)]
tidal_velocity = tidal_height * 0.1 # Simplified relationship
else:
# Fallback: simple sinusoidal tidal model
hours_since_midnight = timestamp.hour + timestamp.minute / 60.0
tidal_height = 2.0 * np.sin(2 * np.pi * hours_since_midnight / 12.4) # 12.4 hour tidal cycle
tidal_velocity = 2.0 * (2 * np.pi / 12.4) * np.cos(2 * np.pi * hours_since_midnight / 12.4)
return tidal_height, tidal_velocity
def process_hydro_data(ship_data, stammdaten, lateralplan, bootstyp, masthoehe, peilung, stroemung_speed):
"""Process hydro data through the complete hydro power model"""
# Get ship dimensions from master data
boat_beam = stammdaten['beam'].iloc[0] # BĆA
boat_length = stammdaten['length_overall'].iloc[0] # LĆA
draft = stammdaten['draft_max'].iloc[0]
# Initialize results
current_speeds = []
dynamic_pressures = []
hydro_forces = []
current_attacks = []
for i in range(len(ship_data)):
# Get current ship data
current_ship_heading = ship_data['heading'].iloc[i]
# Use the consumed current speed from slider
current_speed = stroemung_speed
# Calculate current direction (simplified - could be from bearing)
current_direction = peilung + np.random.uniform(-30, 30) # Add some variation
# Step 1: Calculate dynamic pressure
dynamic_pressure = calculate_dynamic_pressure(current_speed)
# Step 2: Calculate current angle
current_angle = calculate_current_angle(current_ship_heading, current_direction)
# Step 3: Calculate underwater attack surface
attack_surface = calculate_underwater_attack_surface(boat_beam, boat_length, current_angle, draft)
# Step 4: Calculate current force
current_force = calculate_current_force(dynamic_pressure, attack_surface)
# Current attack is the same as current force in this model
current_attack = current_force
# Store results
current_speeds.append(current_speed)
dynamic_pressures.append(dynamic_pressure)
hydro_forces.append(current_force)
current_attacks.append(current_attack)
return current_speeds, hydro_forces, current_attacks
# Create Strƶmung (Current) slider input in this notebook
dc.publish.variable('stroemung_input', 2.0)
dc.publish.card(
type='floatSlider',
label='Current Speed',
unit='m/s',
min=0.0,
max=5.0,
step=0.1,
variable_key='stroemung_input',
logic_view_size=(2,1),
layout=[{"size": (3,2), "position": (0,0), "deck": "default-deck"}]
)
# Create Gezeiten (Tidal) sliders
# Tidal Height
dc.publish.variable('gezeiten_height_input', 1.2)
dc.publish.card(
type='floatSlider',
label='Tidal Height',
unit='m',
min=0.0,
max=3.0,
step=0.1,
variable_key='gezeiten_height_input',
logic_view_size=(2,1),
layout=[{"size": (3,2), "position": (3,0), "deck": "default-deck"}]
)
# Tidal Velocity
dc.publish.variable('gezeiten_velocity_input', 0.5)
dc.publish.card(
type='floatSlider',
label='Tidal Velocity',
unit='m/s',
min=0.0,
max=2.0,
step=0.05,
variable_key='gezeiten_velocity_input',
logic_view_size=(2,1),
layout=[{"size": (3,2), "position": (6,0), "deck": "default-deck"}]
)
# Consume ship data from Schiffsdaten module (as shown in diagram)
lateralplan = dc.consume.variable.lateralplan_input()
bootstyp = dc.consume.variable.bootstyp_input()
masthoehe = dc.consume.variable.masthoehe_input()
peilung = dc.consume.variable.bearing_input()
# Consume ship master data from Schiffsdaten module
vessel_name = dc.consume.variable.vessel_name_input()
vessel_length = dc.consume.variable.vessel_length_input()
vessel_width = dc.consume.variable.vessel_width_input()
gross_tonnage = dc.consume.variable.gross_tonnage_input()
draft = dc.consume.variable.draft_input()
# Consume ship position data from Schiffsdaten module
latitude = dc.consume.variable.latitude_input()
longitude = dc.consume.variable.longitude_input()
# Consume current speed from slider
stroemung_speed = dc.consume.variable.stroemung_input()
# Consume tidal data from sliders
gezeiten_height = dc.consume.variable.gezeiten_height_input()
gezeiten_velocity = dc.consume.variable.gezeiten_velocity_input()
# Create ship master data from consumed variables
stammdaten = pd.DataFrame([{
'vessel_name': vessel_name,
'beam': vessel_width, # BĆA
'length_overall': vessel_length, # LĆA
'gross_tonnage': gross_tonnage,
'draft_max': draft
}])
# Create ship position data from consumed variables
ship_data = pd.DataFrame({
'latitude': [latitude] * 100, # Use consumed latitude
'longitude': [longitude] * 100, # Use consumed longitude
'heading': np.random.uniform(0, 360, 100), # Random heading for now
'speed': np.random.uniform(0, 20, 100) # Random speed for now
})
# Process hydro data through the complete model
stromstaerke, wasserkraft, stroemungsangriff = process_hydro_data(
ship_data, stammdaten, lateralplan, bootstyp, masthoehe, peilung, stroemung_speed
)
# Create structured data for publishing
stromstaerke_data = {
'speed': [float(x) for x in stromstaerke],
'direction': [float(x) for x in [peilung] * len(stromstaerke)] # Use bearing for direction
}
gezeiten = {
'height': [gezeiten_height] * len(stromstaerke), # Use slider value
'velocity': [gezeiten_velocity] * len(stromstaerke) # Use slider value
}
# Publish results
dc.publish.variable('stromstaerke', stromstaerke)
dc.publish.variable('gezeiten', gezeiten)
dc.publish.variable('wasserkraft', wasserkraft)
dc.publish.variable('stroemungsangriff', stroemungsangriff)Module 4: Wassertiefe (Water Depth) - Real-time Depth Analysis
Purpose
Provides real-time water depth analysis using nautical charts, depth models, and current gauge data with feedback loops for dynamic adjustments. Consumes ship position data to determine water depth at the shipās location.
DataCards Input Cards
Kartentiefe (Map Depth) Slider
# Kartentiefe (Map Depth) - Chart Depth
# First publish the variable
dc.publish.variable('kartentiefe_input', 15.0)
# Then publish the card that consumes it
dc.publish.card(
type='floatSlider',
label='Map Depth',
unit='m',
min=5.0,
max=50.0,
step=0.5,
variable_key='kartentiefe_input',
logic_view_size=(2,1),
layout=[{"size": (3,2), "position": (0,0), "deck": "default-deck"}]
)Published Variables
| Variable Name | Type | Description | Source |
|---|---|---|---|
kartentiefe_input | float | Map depth (m) | Slider input |
wassertiefe_berechnet | float | Calculated water depth | Depth model calculations |
tiefenwarnung | dict | Depth warnings and alerts | Depth analysis |
depth_analysis | dict | Complete depth analysis results | Wave and depth calculations |
significant_wave_height | float | Significant wave height H_s | Wave data analysis |
Implementation
# Water Depth Analysis Notebook
import pandas as pd
import numpy as np
import datacards as dc
# Consume ship position data from Schiffsdaten module
latitude = dc.consume.variable.latitude_input()
longitude = dc.consume.variable.longitude_input()
# Consume map depth from slider
kartentiefe = dc.consume.variable.kartentiefe_input()
# Water depth calculation functions based on technical diagrams
def calculate_significant_wave_height(wave_data):
"""
Calculate significant wave height H_s using spectral method.
Formula: H_s = 4 * sqrt(m_0)
Where m_0 is the zeroth moment of the spectrum = variance of sea level
"""
if len(wave_data) == 0:
return 2.0 # Default value for Baltic Sea
# Calculate variance of sea level (m_0)
m_0 = np.var(wave_data)
# Calculate significant wave height
H_s = 4 * np.sqrt(m_0)
return H_s
def calculate_rayleigh_scale_parameter(H_s):
"""
Calculate Rayleigh scale parameter for wave heights.
Formula: Ļ_Ray = H_s / 2
"""
return H_s / 2
def calculate_wave_height_percentile(H_s, percentile=0.95):
"""
Calculate p-percentile of wave height using Rayleigh distribution.
Formula: h_p = H_s * sqrt(-ln(1 - p) / 2)
"""
if percentile >= 1.0:
percentile = percentile / 100.0 # Convert percentage to decimal
h_p = H_s * np.sqrt(-np.log(1 - percentile) / 2)
return h_p
def calculate_maximum_water_column_depth(h_karte, H_s):
"""
Calculate highest possible water column depth.
Formula: h_max = h_Karte + ½ H_s
This gives the greatest depth in the wave trough.
"""
h_max = h_karte + 0.5 * H_s
return h_max
def calculate_effective_water_depth(h_karte, H_s, percentile=0.95):
"""
Calculate effective water depth considering wave effects.
Combines chart depth with wave height percentiles.
"""
# Calculate maximum water column depth
h_max = calculate_maximum_water_column_depth(h_karte, H_s)
# Calculate wave height percentile
h_p = calculate_wave_height_percentile(H_s, percentile)
# Effective depth is the minimum of max depth and chart depth + wave effect
effective_depth = min(h_max, h_karte + h_p)
return effective_depth
# Create wave data for the ship's position (simulated)
# In a real implementation, this would come from Copernicus or other wave services
def get_wave_data_for_position(lat, lon):
"""Get wave data for specific position (simulated)"""
# Simulate wave data based on position
# Baltic Sea typically has H_s = 0.5-3.0m
base_wave_height = 1.5 + 0.5 * np.sin(lat * np.pi / 180) * np.cos(lon * np.pi / 180)
# Generate time series of wave heights
time_points = 100
wave_heights = np.random.normal(base_wave_height, 0.3, time_points)
wave_heights = np.maximum(wave_heights, 0.1) # Ensure positive values
return wave_heights
# Process water depth analysis for ship's current position
print(f"Analyzing water depth at position: {latitude:.4f}°N, {longitude:.4f}°E")
# Get wave data for ship's position
wave_data = get_wave_data_for_position(latitude, longitude)
# Calculate significant wave height
H_s = calculate_significant_wave_height(wave_data)
print(f"Significant wave height (H_s): {H_s:.2f} m")
# Calculate effective water depth
effective_depth = calculate_effective_water_depth(kartentiefe, H_s)
print(f"Chart depth: {kartentiefe:.2f} m")
print(f"Effective water depth: {effective_depth:.2f} m")
# Calculate maximum water column depth
h_max = calculate_maximum_water_column_depth(kartentiefe, H_s)
print(f"Maximum water column depth: {h_max:.2f} m")
# Calculate wave height percentiles
h_95 = calculate_wave_height_percentile(H_s, 0.95)
h_99 = calculate_wave_height_percentile(H_s, 0.99)
print(f"95th percentile wave height: {h_95:.2f} m")
print(f"99th percentile wave height: {h_99:.2f} m")
# Create depth analysis results
depth_analysis = {
'position_lat': latitude,
'position_lon': longitude,
'chart_depth': kartentiefe,
'significant_wave_height': H_s,
'effective_depth': effective_depth,
'max_water_column_depth': h_max,
'wave_height_95th_percentile': h_95,
'wave_height_99th_percentile': h_99,
'analysis_timestamp': pd.Timestamp.now().isoformat()
}
# Create depth warning based on effective depth
if effective_depth < 5.0:
warning_level = "CRITICAL"
warning_message = "Very shallow water - high grounding risk"
elif effective_depth < 10.0:
warning_level = "WARNING"
warning_message = "Shallow water - proceed with caution"
elif effective_depth < 20.0:
warning_level = "CAUTION"
warning_message = "Moderate depth - monitor closely"
else:
warning_level = "SAFE"
warning_message = "Adequate water depth"
tiefenwarnung = {
'level': warning_level,
'message': warning_message,
'effective_depth': effective_depth,
'chart_depth': kartentiefe,
'wave_effect': effective_depth - kartentiefe
}
# Publish results
dc.publish.variable('wassertiefe_berechnet', effective_depth)
dc.publish.variable('tiefenwarnung', tiefenwarnung)
dc.publish.variable('depth_analysis', depth_analysis)
dc.publish.variable('significant_wave_height', H_s)Module 5: Ankermodell (Anchor Model)
Purpose
Calculates anchor positioning, holding power, and anchor requirements based on environmental conditions and ship characteristics.
Published Variables
| Variable Name | Type | Description | Source |
|---|---|---|---|
ankergewicht | float | Required anchor weight (kg) | Environmental force calculations |
ankertyp | string | Recommended anchor type | Seabed and depth analysis |
bodengrund | string | Seabed type classification | Environmental data |
wetterdaten | DataFrame | Weather conditions | Environmental modules |
ankerposition | dict | Recommended anchor position | Position analysis |
DataCards Input Cards
Bodenprofil (Seabed Profile) Slider
# Bodenprofil (Seabed Profile) - Seabed Type
# First publish the variable
dc.publish.variable('bodenprofil_input', 'sand')
# Then publish the card that consumes it
dc.publish.card(
type='combobox',
label='Seabed Profile',
options=['sand', 'schlamm', 'kiesel', 'kies', 'ton', 'lehm'],
variable_key='bodenprofil_input',
logic_view_size=(2,1),
layout=[{"size": (3,2), "position": (0,0), "deck": "default-deck"}]
)KettenlƤnge (Chain Length) Slider
# KettenlƤnge (Chain Length) - Chain Length
# First publish the variable
dc.publish.variable('kettenlaenge_input', 50.0)
# Then publish the card that consumes it
dc.publish.card(
type='floatSlider',
label='Chain Length',
unit='m',
min=10.0,
max=200.0,
step=5.0,
variable_key='kettenlaenge_input',
logic_view_size=(2,1),
layout=[{"size": (3,2), "position": (3,0), "deck": "default-deck"}]
)Implementation
# Anchor Model Notebook
import pandas as pd
import numpy as np
import datacards as dc
# Consume ship master data from Schiffsdaten module
vessel_length = dc.consume.variable.vessel_length_input()
vessel_width = dc.consume.variable.vessel_width_input()
gross_tonnage = dc.consume.variable.gross_tonnage_input()
draft = dc.consume.variable.draft_input()
# Consume water depth from Wassertiefe module
wassertiefe = dc.consume.variable.wassertiefe_berechnet()
# Consume environmental forces from Wind and Hydro modules
windkraft = dc.consume.variable.windkraft()
wasserkraft = dc.consume.variable.wasserkraft()
# Consume input sliders
bodenprofil = dc.consume.variable.bodenprofil_input()
kettenlaenge = dc.consume.variable.kettenlaenge_input()
# Anchor efficiency data based on technical diagrams
def get_anchor_efficiency_data():
"""Get anchor efficiency (N/kg) by anchor type and soil type"""
return {
'Danforth (Fluke)': {'sand': 150, 'schlamm': 150, 'kiesel': 100, 'kies': 100, 'ton': 200, 'lehm': 200},
'CQR (Plow)': {'sand': 120, 'schlamm': 100, 'kiesel': 80, 'kies': 80, 'ton': 150, 'lehm': 150},
'Delta (Plow)': {'sand': 130, 'schlamm': 110, 'kiesel': 90, 'kies': 90, 'ton': 160, 'lehm': 160},
'Bruce (Claw)': {'sand': 100, 'schlamm': 80, 'kiesel': 70, 'kies': 70, 'ton': 120, 'lehm': 120},
'Rocna (Scoop)': {'sand': 140, 'schlamm': 120, 'kiesel': 100, 'kies': 100, 'ton': 180, 'lehm': 180},
'Spade (Scoop)': {'sand': 135, 'schlamm': 115, 'kiesel': 95, 'kies': 95, 'ton': 175, 'lehm': 175},
'Admiralty/Stock': {'sand': 130, 'schlamm': 130, 'kiesel': 60, 'kies': 60, 'ton': 175, 'lehm': 175},
'Mushroom': {'sand': 50, 'schlamm': 200, 'kiesel': 30, 'kies': 30, 'ton': 180, 'lehm': 180},
'Grapnel': {'sand': 80, 'schlamm': 60, 'kiesel': 70, 'kies': 70, 'ton': 100, 'lehm': 100},
'Kedge': {'sand': 90, 'schlamm': 70, 'kiesel': 80, 'kies': 80, 'ton': 110, 'lehm': 110}
}
def calculate_horizontal_force(windkraft, wasserkraft):
"""Calculate total horizontal environmental force Fy"""
# Convert to numpy arrays if needed
if hasattr(windkraft, 'values'):
windkraft = windkraft.values
if hasattr(wasserkraft, 'values'):
wasserkraft = wasserkraft.values
# Ensure same length
min_len = min(len(windkraft), len(wasserkraft))
windkraft = windkraft[:min_len]
wasserkraft = wasserkraft[:min_len]
# Total horizontal force
Fy = windkraft + wasserkraft
return Fy
def calculate_anchor_weight_requirement(Fy, vessel_length, gross_tonnage):
"""Calculate required anchor weight based on vessel size and environmental forces"""
# Base weight calculation: 1 kg per 1000 kg displacement
base_weight = gross_tonnage * 0.001
# Environmental factor: increase weight for higher forces
max_force = np.max(Fy) if len(Fy) > 0 else 1000
force_factor = min(2.0, 1 + (max_force / 2000)) # Cap at 2x
# Size factor: larger vessels need proportionally more anchor weight
size_factor = 1 + (vessel_length / 100) # Increase with length
required_weight = base_weight * force_factor * size_factor
return max(required_weight, 10) # Minimum 10 kg
def calculate_anchor_holding_power(anchor_weight, anchor_type, bodenprofil):
"""Calculate anchor holding power based on weight, type, and soil"""
efficiency_data = get_anchor_efficiency_data()
if anchor_type not in efficiency_data:
anchor_type = 'Danforth (Fluke)' # Default
if bodenprofil not in efficiency_data[anchor_type]:
bodenprofil = 'sand' # Default
efficiency = efficiency_data[anchor_type][bodenprofil] # N/kg
holding_power = efficiency * anchor_weight # N
return holding_power
def calculate_chain_requirements(Fy, wassertiefe, kettenlaenge, bodenprofil):
"""Calculate chain requirements using catenary approach"""
# Chain weight per meter (N/m) - typical values
chain_weights = {
'sand': 25, 'schlamm': 25, 'kiesel': 30, 'kies': 30, 'ton': 20, 'lehm': 20
}
w = chain_weights.get(bodenprofil, 25) # Default 25 N/m
# Ground friction coefficient
friction_coeffs = {
'sand': 0.6, 'schlamm': 0.4, 'kiesel': 0.7, 'kies': 0.7, 'ton': 0.5, 'lehm': 0.5
}
mu = friction_coeffs.get(bodenprofil, 0.6)
# Calculate for maximum force
Fy_max = np.max(Fy) if len(Fy) > 0 else 1000
# Catenary calculations
H = Fy_max # Horizontal force
D = wassertiefe # Water depth
L = kettenlaenge # Chain length
# Parameter a = H/w
a = H / w
# Check if L >= 5*D (standard condition)
if L < 5 * D:
return {
'status': 'WARNING',
'message': f'Chain length {L}m is less than 5x depth {D}m. Minimum recommended: {5*D:.1f}m',
'effective_length': L,
'required_length': 5 * D
}
# Calculate effective chain length using catenary
# cosh(x/a) = 1 + D/a + x/a
# This is solved iteratively
x = D * a # Initial guess
for _ in range(10): # Iterative solution
x_new = a * np.arccosh(1 + D/a + x/a)
if abs(x_new - x) < 0.001:
break
x = x_new
L_eff = a * np.sinh(x/a) # Effective chain length
V = w * np.sinh(x/a) # Vertical component at anchor
T_anker = np.sqrt(H**2 + V**2) # Total tension at anchor
# Calculate friction from resting chain
if L > L_eff:
F_reibung = mu * w * (L - L_eff)
T_anker_effective = T_anker - F_reibung # Reduced tension due to friction
else:
F_reibung = 0
T_anker_effective = T_anker
return {
'status': 'OK',
'effective_length': L_eff,
'total_tension': T_anker,
'effective_tension': T_anker_effective,
'friction_force': F_reibung,
'vertical_component': V,
'horizontal_component': H
}
def determine_optimal_anchor_type(bodenprofil, vessel_length):
"""Determine optimal anchor type based on soil and vessel size"""
# Anchor type recommendations by soil
soil_recommendations = {
'sand': 'Danforth (Fluke)',
'schlamm': 'Mushroom',
'kiesel': 'Bruce (Claw)',
'kies': 'Bruce (Claw)',
'ton': 'Rocna (Scoop)',
'lehm': 'Rocna (Scoop)'
}
# For very large vessels, prefer more robust types
if vessel_length > 80:
if bodenprofil in ['sand', 'schlamm']:
return 'Rocna (Scoop)'
elif bodenprofil in ['kiesel', 'kies']:
return 'Delta (Plow)'
return soil_recommendations.get(bodenprofil, 'Danforth (Fluke)')
# Main anchor model calculations
print(f"=== ANCHOR MODEL CALCULATIONS ===")
print(f"Vessel: {vessel_length}m LOA, {gross_tonnage} GT")
print(f"Water depth: {wassertiefe:.2f} m")
print(f"Seabed profile: {bodenprofil}")
print(f"Chain length: {kettenlaenge:.1f} m")
# Calculate horizontal environmental force
Fy = calculate_horizontal_force(windkraft, wasserkraft)
print(f"Maximum horizontal force: {np.max(Fy):.0f} N")
# Calculate required anchor weight
ankergewicht = calculate_anchor_weight_requirement(Fy, vessel_length, gross_tonnage)
print(f"Required anchor weight: {ankergewicht:.1f} kg")
# Determine optimal anchor type
ankertyp = determine_optimal_anchor_type(bodenprofil, vessel_length)
print(f"Recommended anchor type: {ankertyp}")
# Calculate anchor holding power
holding_power = calculate_anchor_holding_power(ankergewicht, ankertyp, bodenprofil)
print(f"Anchor holding power: {holding_power:.0f} N")
# Calculate chain requirements
chain_analysis = calculate_chain_requirements(Fy, wassertiefe, kettenlaenge, bodenprofil)
print(f"Chain analysis: {chain_analysis['status']}")
# Calculate safety factor
safety_factor = holding_power / np.max(Fy) if np.max(Fy) > 0 else float('inf')
print(f"Safety factor: {safety_factor:.2f}")
# Determine safety status
if safety_factor >= 3.0:
safety_status = "SAFE"
safety_message = "Adequate holding power"
elif safety_factor >= 2.0:
safety_status = "CAUTION"
safety_message = "Marginal holding power - monitor conditions"
else:
safety_status = "DANGER"
safety_message = "Insufficient holding power - consider relocating"
# Create anchor position recommendation
ankerposition = {
'latitude': 54.0, # Would come from ship position
'longitude': 10.0, # Would come from ship position
'water_depth': wassertiefe,
'seabed_type': bodenprofil,
'safety_factor': safety_factor,
'safety_status': safety_status,
'safety_message': safety_message
}
# Create weather data summary
wetterdaten = {
'max_wind_force': float(np.max(windkraft)) if len(windkraft) > 0 else 0,
'max_water_force': float(np.max(wasserkraft)) if len(wasserkraft) > 0 else 0,
'total_environmental_force': float(np.max(Fy)) if len(Fy) > 0 else 0,
'analysis_timestamp': pd.Timestamp.now().isoformat()
}
# Publish results
dc.publish.variable('ankergewicht', ankergewicht)
dc.publish.variable('ankertyp', ankertyp)
dc.publish.variable('bodengrund', bodenprofil)
dc.publish.variable('wetterdaten', wetterdaten)
dc.publish.variable('ankerposition', ankerposition)
print(f"\n=== ANCHOR MODEL RESULTS ===")
print(f"Anchor Weight: {ankergewicht:.1f} kg")
print(f"Anchor Type: {ankertyp}")
print(f"Seabed: {bodenprofil}")
print(f"Safety Status: {safety_status}")
print(f"Safety Factor: {safety_factor:.2f}")
print(f"Chain Status: {chain_analysis['status']}")Module 6: Datenprodukt (Data Product) - Anchor Situation Analysis
Purpose
Creates the final data product that combines environmental forces (Gegenstress) with anchor resources (Ankerressource) to produce a comprehensive table and text showing how to correctly anchor in the current situation.
Published Variables
| Variable Name | Type | Description | Source |
|---|---|---|---|
gegenstress | Dict | Environmental stress forces | Wind + Hydro modules |
ankerressource | Dict | Anchor capacity and resources | Anchor model |
bruchlast | Float | Breaking load calculation | Stress vs Resource analysis |
anker_situation_table | DataFrame | Complete anchoring situation table | Data product analysis |
anker_empfehlung | String | Text recommendation for anchoring | Situation analysis |
DataCards Input Cards
Safety Factor Slider
# Safety Factor - Additional safety margin
# First publish the variable
dc.publish.variable('safety_factor_input', 3.0)
# Then publish the card that consumes it
dc.publish.card(
type='floatSlider',
label='Safety Factor',
unit='',
min=1.5,
max=5.0,
step=0.1,
variable_key='safety_factor_input',
logic_view_size=(2,1),
layout=[{"size": (3,2), "position": (0,0), "deck": "default-deck"}]
)Implementation
# Data Product Notebook - Final Anchor Situation Analysis
import pandas as pd
import numpy as np
import datacards as dc
from datetime import datetime
# Consume environmental forces (Gegenstress)
windkraft = dc.consume.variable.windkraft()
wasserkraft = dc.consume.variable.wasserkraft()
# Consume anchor resources (Ankerressource)
ankergewicht = dc.consume.variable.ankergewicht()
ankertyp = dc.consume.variable.ankertyp()
bodengrund = dc.consume.variable.bodengrund()
wassertiefe = dc.consume.variable.wassertiefe_berechnet()
# Consume ship data
vessel_length = dc.consume.variable.vessel_length_input()
vessel_width = dc.consume.variable.vessel_width_input()
gross_tonnage = dc.consume.variable.gross_tonnage_input()
# Consume safety factor
safety_factor = dc.consume.variable.safety_factor_input()
def calculate_gegenstress(windkraft, wasserkraft):
"""Calculate total environmental stress (Gegenstress)"""
# Convert to numpy arrays if needed
if hasattr(windkraft, 'values'):
windkraft = windkraft.values
if hasattr(wasserkraft, 'values'):
wasserkraft = wasserkraft.values
# Ensure same length
min_len = min(len(windkraft), len(wasserkraft))
windkraft = windkraft[:min_len]
wasserkraft = wasserkraft[:min_len]
# Total environmental stress
total_stress = windkraft + wasserkraft
return {
'wind_stress': float(np.max(windkraft)) if len(windkraft) > 0 else 0,
'water_stress': float(np.max(wasserkraft)) if len(wasserkraft) > 0 else 0,
'total_stress': float(np.max(total_stress)) if len(total_stress) > 0 else 0,
'max_wind': float(np.max(windkraft)) if len(windkraft) > 0 else 0,
'max_water': float(np.max(wasserkraft)) if len(wasserkraft) > 0 else 0,
'analysis_timestamp': datetime.now().isoformat()
}
def calculate_ankerressource(ankergewicht, ankertyp, bodengrund, wassertiefe, vessel_length):
"""Calculate anchor resources (Ankerressource)"""
# Anchor efficiency data (N/kg)
efficiency_data = {
'Danforth (Fluke)': {'sand': 150, 'schlamm': 150, 'kiesel': 100, 'kies': 100, 'ton': 200, 'lehm': 200},
'CQR (Plow)': {'sand': 120, 'schlamm': 100, 'kiesel': 80, 'kies': 80, 'ton': 150, 'lehm': 150},
'Delta (Plow)': {'sand': 130, 'schlamm': 110, 'kiesel': 90, 'kies': 90, 'ton': 160, 'lehm': 160},
'Bruce (Claw)': {'sand': 100, 'schlamm': 80, 'kiesel': 70, 'kies': 70, 'ton': 120, 'lehm': 120},
'Rocna (Scoop)': {'sand': 140, 'schlamm': 120, 'kiesel': 100, 'kies': 100, 'ton': 180, 'lehm': 180},
'Spade (Scoop)': {'sand': 135, 'schlamm': 115, 'kiesel': 95, 'kies': 95, 'ton': 175, 'lehm': 175},
'Admiralty/Stock': {'sand': 130, 'schlamm': 130, 'kiesel': 60, 'kies': 60, 'ton': 175, 'lehm': 175},
'Mushroom': {'sand': 50, 'schlamm': 200, 'kiesel': 30, 'kies': 30, 'ton': 180, 'lehm': 180},
'Grapnel': {'sand': 80, 'schlamm': 60, 'kiesel': 70, 'kies': 70, 'ton': 100, 'lehm': 100},
'Kedge': {'sand': 90, 'schlamm': 70, 'kiesel': 80, 'kies': 80, 'ton': 110, 'lehm': 110}
}
# Get efficiency for current anchor type and soil
if ankertyp in efficiency_data and bodengrund in efficiency_data[ankertyp]:
efficiency = efficiency_data[ankertyp][bodengrund]
else:
efficiency = 150 # Default efficiency
# Calculate holding power
holding_power = efficiency * ankergewicht # N
# Calculate chain strength (simplified)
chain_diameter = max(8, vessel_length / 20) # mm
chain_strength = chain_diameter * chain_diameter * 0.785 * 400 # N (simplified)
# Calculate scope requirements
scope_ratio = 6.0 # Standard scope ratio
required_scope = wassertiefe * scope_ratio
return {
'anchor_weight': float(ankergewicht),
'anchor_type': str(ankertyp),
'seabed_type': str(bodengrund),
'efficiency': float(efficiency),
'holding_power': float(holding_power),
'chain_diameter': float(chain_diameter),
'chain_strength': float(chain_strength),
'water_depth': float(wassertiefe),
'required_scope': float(required_scope),
'total_capacity': float(holding_power + chain_strength)
}
def calculate_bruchlast(gegenstress, ankerressource, safety_factor):
"""Calculate breaking load (Bruchlast) - the critical limit"""
total_stress = gegenstress['total_stress']
total_capacity = ankerressource['total_capacity']
# Calculate safety factor
actual_safety_factor = total_capacity / total_stress if total_stress > 0 else float('inf')
# Calculate breaking load
breaking_load = total_capacity / safety_factor
return {
'environmental_stress': total_stress,
'anchor_capacity': total_capacity,
'actual_safety_factor': actual_safety_factor,
'required_safety_factor': safety_factor,
'breaking_load': breaking_load,
'safety_status': 'SAFE' if actual_safety_factor >= safety_factor else 'DANGER'
}
def create_anker_situation_table(gegenstress, ankerressource, bruchlast):
"""Create comprehensive anchoring situation table"""
# Define all parameters with consistent structure
parameters = [
'Environmental Stress (Gegenstress)',
' - Wind Force',
' - Water Force',
' - Total Environmental Force',
'',
'Anchor Resources (Ankerressource)',
' - Anchor Weight',
' - Anchor Type',
' - Seabed Type',
' - Holding Power',
' - Chain Strength',
' - Total Capacity',
'',
'Safety Analysis',
' - Actual Safety Factor',
' - Required Safety Factor',
' - Breaking Load',
' - Safety Status'
]
values = [
f"{gegenstress['total_stress']:.0f} N",
f"{gegenstress['wind_stress']:.0f} N",
f"{gegenstress['water_stress']:.0f} N",
f"{gegenstress['total_stress']:.0f} N",
'',
'',
f"{ankerressource['anchor_weight']:.1f} kg",
ankerressource['anchor_type'],
ankerressource['seabed_type'],
f"{ankerressource['holding_power']:.0f} N",
f"{ankerressource['chain_strength']:.0f} N",
f"{ankerressource['total_capacity']:.0f} N",
'',
f"{bruchlast['actual_safety_factor']:.2f}",
f"{bruchlast['required_safety_factor']:.1f}",
f"{bruchlast['breaking_load']:.0f} N",
bruchlast['safety_status']
]
units = [
'',
'',
'',
'',
'',
'',
'kg',
'',
'',
'N',
'N',
'N',
'',
'',
'',
'N',
''
]
# Ensure all arrays have the same length
max_length = max(len(parameters), len(values), len(units))
# Pad shorter arrays with empty strings
while len(parameters) < max_length:
parameters.append('')
while len(values) < max_length:
values.append('')
while len(units) < max_length:
units.append('')
table_data = {
'Parameter': parameters,
'Value': values,
'Unit': units
}
return pd.DataFrame(table_data)
def generate_anker_empfehlung(gegenstress, ankerressource, bruchlast):
"""Generate text recommendation for anchoring"""
safety_status = bruchlast['safety_status']
actual_sf = bruchlast['actual_safety_factor']
required_sf = bruchlast['required_safety_factor']
if safety_status == 'SAFE':
recommendation = f"""
ā
ANCHORING SITUATION: SAFE
Current environmental stress: {gegenstress['total_stress']:.0f} N
Anchor capacity: {ankerressource['total_capacity']:.0f} N
Safety factor: {actual_sf:.2f} (required: {required_sf:.1f})
RECOMMENDATION: Proceed with anchoring
- Use {ankerressource['anchor_type']} anchor
- Deploy {ankerressource['required_scope']:.0f}m of chain (6:1 scope)
- Monitor conditions for changes
- Anchor weight {ankerressource['anchor_weight']:.1f}kg is adequate
"""
else:
recommendation = f"""
ā ļø ANCHORING SITUATION: DANGER
Current environmental stress: {gegenstress['total_stress']:.0f} N
Anchor capacity: {ankerressource['total_capacity']:.0f} N
Safety factor: {actual_sf:.2f} (required: {required_sf:.1f})
RECOMMENDATION: DO NOT ANCHOR - Find alternative location
- Environmental forces exceed anchor capacity
- Consider relocating to sheltered area
- Increase anchor weight to at least {gegenstress['total_stress'] * required_sf / ankerressource['efficiency']:.1f}kg
- Or wait for calmer conditions
"""
return recommendation
# Main data product calculations
print("=== MARITIME DATA PRODUCT - ANCHOR SITUATION ANALYSIS ===")
# Calculate Gegenstress (Environmental Stress)
gegenstress = calculate_gegenstress(windkraft, wasserkraft)
print(f"Environmental Stress: {gegenstress['total_stress']:.0f} N")
# Calculate Ankerressource (Anchor Resources)
ankerressource = calculate_ankerressource(ankergewicht, ankertyp, bodengrund, wassertiefe, vessel_length)
print(f"Anchor Capacity: {ankerressource['total_capacity']:.0f} N")
# Calculate Bruchlast (Breaking Load)
bruchlast = calculate_bruchlast(gegenstress, ankerressource, safety_factor)
print(f"Safety Factor: {bruchlast['actual_safety_factor']:.2f}")
# Create anchoring situation table
anker_situation_table = create_anker_situation_table(gegenstress, ankerressource, bruchlast)
# Generate anchoring recommendation
anker_empfehlung = generate_anker_empfehlung(gegenstress, ankerressource, bruchlast)
# Publish results
dc.publish.variable('gegenstress', gegenstress)
dc.publish.variable('ankerressource', ankerressource)
dc.publish.variable('bruchlast', bruchlast)
dc.publish.variable('anker_situation_table', anker_situation_table.to_dict('records'))
dc.publish.variable('anker_empfehlung', anker_empfehlung)
**Publish Environmental Stress card**# First publish the variable
# Publish Environmental Stress card
dc.publish.card(
type='text',
label='Environmental Stress (Gegenstress)',
text=f"Wind: {gegenstress['wind_stress']:.0f} N<br/>Water: {gegenstress['water_stress']:.0f} N<br/>Total: {gegenstress['total_stress']:.0f} N",
variable_key='gegenstress_display',
logic_view_size=(2,1),
layout=[{"size": (3,2), "position": (4,0), "deck": "default-deck"}]
)Publish Anchor Resources card
# First publish the variable
dc.publish.card(
type='text',
label='Anchor Resources (Ankerressource)',
text=f"Weight: {ankerressource['anchor_weight']:.1f} kg<br/>Type: {ankerressource['anchor_type']}<br/>Seabed: {ankerressource['seabed_type']}<br/>Holding Power: {ankerressource['holding_power']:.0f} N<br/>Chain Strength: {ankerressource['chain_strength']:.0f} N<br/>Total Capacity: {ankerressource['total_capacity']:.0f} N",
variable_key='ankerressource_display',
logic_view_size=(2,1),
layout=[{"size": (3,2), "position": (7,0), "deck": "default-deck"}]
)# Publish Safety Analysis card
dc.publish.card(
type='text',
label='Safety Analysis',
text=f"Actual Safety Factor: {bruchlast['actual_safety_factor']:.2f}<br/>Required Safety Factor: {bruchlast['required_safety_factor']:.1f}<br/>Breaking Load: {bruchlast['breaking_load']:.0f} N<br/>Status: {bruchlast['safety_status']}",
variable_key='safety_analysis_display',
logic_view_size=(2,1),
layout=[{"size": (3,2), "position": (10,0), "deck": "default-deck"}]
)
# Publish Anchoring Recommendation card
# Clean up the recommendation text for DataCards display
recommendation_clean = anker_empfehlung.replace('\n', '<br/>').strip()
recommendation_short = recommendation_clean[:300] + "..." if len(recommendation_clean) > 300 else recommendation_cleandc.publish.card(
type='text',
label='Anchoring Recommendation',
text=recommendation_short,
variable_key='anchoring_recommendation_display',
logic_view_size=(2,1),
layout=[{"size": (6,3), "position": (4,2), "deck": "default-deck"}]
)