ZanderNav uses vector triangle navigation to compute two related quantities for each leg.
The Course to Steer is the compass heading the helmsman sets β it points deliberately upstream into the tide.
The Speed Over Land is how fast the boat actually advances along the intended track.
Both are derived from the same vector triangle of boat speed, tide vector and intended track.
1. Course Over Land (COL)
The COL is the great-circle bearing from the departure waypoint to the destination waypoint. It describes the direction the boat actually moves across the chart, regardless of what the helmsman steers.
// Haversine great-circle bearing
ΞΞ» = lonβ β lonβ (in radians)
y = sin(ΞΞ») Β· cos(latβ)
x = cos(latβ)Β·sin(latβ) β sin(latβ)Β·cos(latβ)Β·cos(ΞΞ»)
COL = atan2(y, x) (converted to 0β360Β°)
2. Tidal Stream Interpolation
Tide speed is linearly interpolated between the neap speed (at coefficient 45) and spring speed (at coefficient 95) using the day's tidal coefficient.
f = (coefficient β 45) / 50
tide_speed = neap_speed + f Γ (spring_speed β neap_speed)
// f = 0 at neap (coeff 45), f = 1 at spring (coeff 95)
3. Tide Vector Components
The tide vector is resolved into two components relative to the intended track (COL):
ΞΈ = tide_direction β COL (relative angle)
along = tide_speed Β· cos(ΞΈ) // + = with you, β = against you
cross = tide_speed Β· sin(ΞΈ) // + = pushes right, β = pushes left
4. Course to Steer (CTS)
If the tide pushes the boat sideways, the helmsman must aim the bow upstream to compensate. The correction angle is found by resolving the vector triangle: bow direction + tide vector = track direction.
correction = βarcsin( cross / engine_speed )
CTS = (COL + correction) mod 360
// Negative cross (tide from right) β positive correction β steer right of COL
// If |cross| β₯ engine_speed, tide overwhelms; CTS = COL (fallback)
5. Speed Over Land (SOL)
The boat's effective forward speed along the track. By steering CTS, the cross-tide component is neutralised; only the along-track component of the tide affects progress. The reduced engine contribution (engine must fight the cross-tide) combines with the along-track tide component:
SOL = β(engine_speedΒ² β crossΒ²) + along
// β(engineΒ² β crossΒ²) = engine contribution along the track
// + along = tide helping or hindering forward progress
// If |cross| β₯ engine_speed: SOL = max(0.1, along) (fallback)
6. Sailing Speed & Point of Sail
In sail mode the engine speed is replaced by boat speed through water, determined by the wind strength and the angle of the wind to the bow (point of sail). The no-go zone β within 55Β° of the headwind β returns zero boat speed; all other angles use the polar table below.
| Wind |
π« No-go <55Β° |
Close-hauled 55β65Β° |
Close reach 65β85Β° |
Beam reach 85β115Β° |
Broad reach 115β155Β° |
Run 155β180Β° |
| Calm 0β3 kn | 0 | 0.0 | 0.5 | 0.8 | 0.6 | 0.5 |
| F2 4β7 kn | 0 | 2.0 | 2.8 | 3.2 | 2.8 | 2.4 |
| F3 8β10 kn | 0 | 3.0 | 3.8 | 4.5 | 4.0 | 3.5 |
| F4 11β16 kn | 0 | 3.8 | 4.8 | 5.5 | 5.0 | 4.5 |
| F5 17β21 kn | 0 | 4.0 | 5.2 | 6.0 | 5.5 | 5.0 |
| F6+ β₯22 kn | 0 | 3.5 | 5.2 | 6.0 | 5.5 | 5.0 |
Values are boat speed through water in knots, calibrated against actual GPX tracks from Dell Quay Sailing Club dinghy events. Intermediate wind speeds are linearly interpolated between bands. The resulting boat speed feeds into the same SOL vector triangle as engine mode β tide still applies on top.
Upwind tacking
When a leg is within the no-go zone, ZanderNav simulates a tacking strategy: the boat alternates between the two close-hauled headings (wind Β± 55Β°), bouncing off corridor walls, with a 10-second penalty per tack. The better of port-first or starboard-first is kept. VMG-aware logic suppresses tacks where one heading has negative progress toward the destination.
β¦ Worked Example: West Pole β Dean Elbow
Departure waypoint
West Pole
50.7567Β°N, 0.9375Β°W
Destination waypoint
Dean Elbow
50.7292Β°N, 1.0306Β°W
Assumed conditions
Engine speed = 4.0 kn Β· Tidal coefficient = 70 (moderate)
Tidal stream at HWβ2: neap 0.7 kn, spring 1.5 kn, direction 135Β° (SE)
Step 1 β Distance (Haversine)
Ξlat = 50.7292 β 50.7567 = β0.0275Β°
Ξlon = β1.0306 β (β0.9375) = β0.0931Β°
a = sinΒ²(Ξlat/2) + cos(50.7567Β°)Β·cos(50.7292Β°)Β·sinΒ²(Ξlon/2)
= sinΒ²(β0.01375Β°) + 0.6321 Γ 0.6327 Γ sinΒ²(β0.04655Β°)
= 0.0000057 + 0.3998 Γ 0.000000659 = 0.0000059
dist = 2 Γ 6371 Γ arcsin(β0.0000059) = 3.90 nm
β Verify with Google AI
Step 2 β Course Over Land
ΞΞ» = β0.0931Β° = β0.001625 rad
y = sin(β0.001625) Γ cos(50.7292Β°) = β0.001625 Γ 0.6327 = β0.001028
x = cos(50.7567Β°)Γsin(50.7292Β°) β sin(50.7567Β°)Γcos(50.7292Β°)Γcos(β0.001625)
= 0.6321Γ0.7746 β 0.7749Γ0.6327Γ0.9999 = 0.4895 β 0.4902 = β0.000701
COL = atan2(β0.001028, β0.000701) = 235Β° + 360Β° β‘ 245.0Β° (WSW)
β Verify with Google AI
Step 3 β Tidal Stream Interpolation
f = (70 β 45) / 50 = 0.50
tide_speed = 0.7 + 0.50 Γ (1.5 β 0.7) = 0.7 + 0.40 = 1.10 kn @ 135Β° (SE)
β Verify with Google AI
Step 4 β Tide Vector Components
ΞΈ = tide_dir β COL = 135Β° β 245Β° = β110Β°
along = 1.10 Γ cos(β110Β°) = 1.10 Γ (β0.342) = β0.376 kn (against track)
cross = 1.10 Γ sin(β110Β°) = 1.10 Γ (β0.940) = β1.034 kn (pushing left of track)
Step 5 β Course to Steer
|cross| = 1.034 < engine 4.0 β
correction = βarcsin(β1.034 / 4.0) = βarcsin(β0.2584) = +14.98Β°
(tide pushes left, so steer right: positive correction)
CTS = 245.0Β° + 14.98Β° = 260.0Β° (W)
β Helmsman steers 260Β° to track 245Β° over ground
β Verify with Google AI
Step 6 β Speed Over Land
SOL = β(4.0Β² β (β1.034)Β²) + (β0.376)
= β(16.00 β 1.069) + (β0.376)
= β14.931 + (β0.376)
= 3.864 β 0.376 = 3.49 kn
Journey time = 3.90 nm Γ· 3.49 kn = 67 min
β Verify with Google AI
Result summary
Distance: 3.90 nm
COL (track): 245Β° WSW
CTS (steer): 260Β° W (+15Β° correction for tide)
Tide: 1.10 kn SE @ 135Β°
SOL: 3.49 kn
Journey time: 67 min
All calculations are performed in real time for every leg using the departure time, interpolated tidal coefficient, and the nearest tidal stream data point. Formulae match the vector triangle method used in the source spreadsheet (zandernav8.xlsm Plan sheet, columns ANβBC).
π§ͺ A* TACTICAL TACK OPTIMIZER β experimental
The default tack overlay uses a greedy march: from each waypoint, sail close-hauled until you hit the corridor wall, then flip tack. It's fast but treats each leg in isolation and can over-sail in narrow water. An A* search finds the provably time-optimal path across the navigable area.
How it works:
- Grid. The harbour is discretized at
ASTAR_GRID_M = 25 m. Cells outside WATER_BOUNDARIES (the existing KML-derived polygons) are unreachable β the boundary is the constraint.
- Nodes. Each node is
(cellX, cellY, lastHeadingIdx) using 32 quantized headings. Tracking the arrival heading lets the search distinguish "the same cell reached from a different tack" and charge tack penalties correctly.
- Edges. From each cell, 32 candidate neighbour cells (one per heading) are considered. Edges in the no-go zone (within 45Β° of the wind axis) are skipped. Edge cost = distance Γ·
tackSOL + 10 s penalty when the move crosses the wind axis vs. the previous heading.
- Heuristic. Admissible lower bound: straight-line distance to the gate Γ· best polar speed for the bearing to it. If that bearing is in the no-go zone, the bound uses the VMG of the close-hauled lay line,
vch Β· cos(55Β° β ΞΈ) where ΞΈ is its angle off the wind. Admissibility guarantees A* returns the true optimum.
- Search. Min-heap priority queue, expanding lowest
f = g + h first. Capped at ASTAR_MAX_EXPANDED = 80 000 nodes per leg for browser performance.
- Gate target (not a point). Each destination waypoint is treated as a broad gate β the green β₯ line across the channel, perpendicular to the leg and spanning the full navigable width (from
boundaryHalfWidth, floored at 25 m). The search succeeds the moment it crosses that line anywhere within the gate, not only at the waypoint cell, so it can lay the mark at whichever side of the channel is fastest. Consecutive tacking legs are then chained start-to-gate-exit, matching how the greedy overlay carries position forward.
When it helps over the greedy: narrow channels with curved shorelines (chi_wst, the Bosham fork), legs where the optimal tack length is shorter than the corridor wall, and chained upwind sequences where the greedy's per-leg locality forces an unnecessary waypoint hit. For clear-water beam reaches the two algorithms agree to within grid quantization (β²25 s on a 1 nm leg).
Caveats. Tide is sampled once per leg, not continuously along the path; for long upwind beats this can under-state the gain from using the favourable side. Heading quantization (11.25Β° steps) costs a few percent vs. the continuous-bearing greedy on perfectly clear water. Time-dependent state (lat, lon, tack, hour-of-tide) would close both gaps but multiplies the state space; not implemented in v1.
Toggle: π§ͺ A* tacks checkbox (appears beside β΅ Tacks when sailing). Programmatic: window.ASTAR_ENABLED = true; runAStarOnCurrentJourney();