Skip to content

Planning Form

Marco edited this page Oct 3, 2026 · 9 revisions

🇮🇱 עברית

Flight planning form

The wind-triangle sheet: calibrated airspeed in, compass heading out, one row per segment. It is the document an Israeli CVFR written exercise asks for, and the one a pilot works a real departure out on.

Open it with 📐 Flight planning form in the Charts menu. Like the flight plan it needs a route: with no legs it says No legs yet — drop at least two waypoints first and opens nothing.

The flight planning form

It is not the flight plan

They are different documents and both are kept.

Flight plan Flight planning form
What it is the route as flown: magnetic heading, distance, time, fuel the arithmetic between an indicated airspeed and a compass heading
Wind none, deliberately — a printed plan must not change because a weather fetch landed the whole point of it
Where it is used in the air at the desk, before the flight

That zero-wind rule is why this exists at all rather than as twelve more columns on the plan.

The 23 columns

LEG · From · To · CAS · PA · Temp · TAS · W kt · W dir · TT · Drift · TH · Var · MH · Dev · CH · GS · Dist · Time · Cum time · FF · Fuel · Cum fuel

In that order, with those Hebrew names, because that is the order and the wording of the sheet the exercise hands out. Climb rows are tinted green, descent rows amber, so the three kinds of segment read apart at a glance.

The arithmetic, and why each rule is the rule

Every one of these reproduces a published answer sheet. Where an obvious alternative exists, it is named here because it does not.

True airspeed is CAS ÷ √σ, where σ is the density ratio at the segment's density altitude:

ISA temp at PA   = 15 − 2 °C per 1000 ft
density altitude = PA + 120 × (OAT − ISA temp)
σ                = (1 − 6.8756e−6 × DA) ^ 4.2561

Two degrees per thousand feet, not the physical 1.98: the figure the exercise is worked in and the one a pilot can check without a calculator. The two differ in the first decimal of a TAS at one place on the reference sheet — 6,000 ft, where 1.98 gives 98.24 and 2 gives 98.26, printing 98.2 and 98.3. The in-flight altimetry correction keeps 1.98; it is flown against rather than checked by hand.

Where each segment reads its air. The climb takes two thirds of the height gained, above the field it left; the descent takes half the height lost, above the field it is landing at. Both fractions are fields in the form, in percent, and both start at the exercise's figures.

The met table and the compass card are read at their nearest entry, never interpolated. This is not an approximation: the reference sheet's MH 050 takes the 060 entry's deviation and prints 049, where interpolating between 030 and 060 gives 048. Ties go to the lower row, so the choice is deterministic.

Drift is the side the wind pushes you, which is the opposite sign to the wind-correction angle the app computes — and it is derived from the two columns beside it, so the sheet adds up: track − drift = true heading. Deviation is written the way variation is (2E, not +2) because east subtracts in both and a pilot applies them the same way.

One top of climb. The climb off the departure field is the only ramp: it is the one place the aeroplane demonstrably leaves a known elevation. A later leg planned higher or lower is flown at its own level and gets no ramp of its own, because you have said what each leg is flown at, not where inside it the level changes — and a sheet that guesses prints a TOC at every waypoint. The descent onto the destination is the ramp at the other end.

Climb and descent are bounded by time, not distance: a rate and a height. Their distance is whatever ground speed they make, which is why a climb can outlast the first leg and carry on down the next leg's track.

What it says when it cannot work something out

A sheet that is merely short still adds up, and says nothing about why. So:

  • a crosswind stronger than the airspeed prints no solution in the ground-speed cell;
  • a climb with no rate of climb prints no rate;
  • a descent that does not fit in the route marks its last row, because the aeroplane arrives above the field;

and a note under the sheet says which of these happened, in the colour the app warns in.

The met table

Typed, because an exercise hands you one. Sorted by altitude, however it arrives.

Add a level adds a level this flight actually reads its air at — the first of the altitudes the sheet asks the forecast for that has no row yet, falling back to a thousand feet above the top once the flight is covered. The new row's temperature is ISA for that altitude, not a flat 15 °C: every true airspeed on the sheet comes out of that column, so a wrong number there is a wrong number everywhere. The wind stays calm, which claims nothing. The caret lands in the new row's altitude, and the button says which level it is about to add.

Fetch forecast fills it from the same Open-Meteo pressure-level data the wind overlay and the density-altitude panel use — at the middle of the route, at the hour the shared look-ahead clock is pointing at, for exactly the altitudes the sheet reads its air at (every row's pressure altitude, bracketed by one level above and below so the nearest-row lookup always has a neighbour). Planning is done for a departure that has not happened yet, which is why the slider and not "now" decides.

It fills the table, never the arithmetic directly: what was fetched is then visible, editable and saved, and each temperature and wind cell says in its tooltip where it came from — the typed table, the route wind, or the standard atmosphere. A forecast a pilot cannot see or correct is not planning data. A fetch that fails leaves the table exactly as it was.

The compass card

Twelve marks at the 30° headings a card is swung on, laid out two pairs of columns wide as it is printed. Only the "steer" column is typed, in three digits like the magnetic column beside it, and it stays on the circle: type 364 and it reads 004, step up from 359 and it reads 000. There is no maximum on the box, because a maximum would make the spinner stop at 359 and a compass does not stop.

Deviation starts at zero everywhere: a made-up card looks exactly like data. Each steer box has its own ↺, lit only while that heading carries a deviation, and the one beside the card's title clears the whole table back to steer-what-you-are-told. Emptying a box does the same for its row.

What the form fills in for you

Field Where it comes from
Departure / destination elevation the airfield dataset, when the endpoint is a field we know
Cruise altitude the route's own legs — there is no field for it, because two places to set one altitude is two numbers to disagree
Variation the app's own magneticVariationDeg, negated: the app signs it magnetic = true + variation, so its −5 is 5E on a sheet
Cruise burn the aircraft profile

A typed value is a decision and outlives the route. The ↺ beside a field hands it back to what the app knows; it is always there, dimmed while there is nothing to undo, and emptying the box does the same thing.

Try it: the Herzliya → Rosh Pina exercise

The exercise file — the route (5 points, legs at 6,000 ft), the met table, the compass card, 4°E variation, and the climb and descent figures the sheet states. Open it with Import/Export → Open: the route lands on the map with its altitudes, and the form fills in.

Saving it. GitHub serves that link as plain text, so a browser shows it rather than downloading it — right-click the link → Save link as…, or open it and save the page. Or copy it out of the block below into a file ending in .json; the app reads the text, not the name.

The file, to copy
{
  "waypoints": [
    {
      "lat": 32.17944,
      "lng": 34.83444,
      "name": "LLHZ"
    },
    {
      "lat": 32.5,
      "lng": 35,
      "name": "א"
    },
    {
      "lat": 32.66667,
      "lng": 35.5,
      "name": "ב"
    },
    {
      "lat": 32.96667,
      "lng": 35.16667,
      "name": "ג"
    },
    {
      "lat": 32.98111,
      "lng": 35.57194,
      "name": "LLIB"
    }
  ],
  "legs": [
    {
      "inboundAltitude": 6000,
      "outboundAltitude": 6000,
      "flightSpeed": 90,
      "outboundSpeed": 90,
      "inLabel": {
        "a": 0,
        "_default": 1,
        "_m": 1
      },
      "outLabel": {
        "a": 0,
        "_default": 1,
        "_m": 1
      },
      "speedAuto": 1,
      "retSpeedAuto": 1,
      "cumLabel": {
        "a": 0,
        "_default": 1,
        "_m": 1
      },
      "cumLabelRet": {
        "a": 0,
        "_default": 1,
        "_m": 1
      }
    },
    {
      "inboundAltitude": 6000,
      "outboundAltitude": 6000,
      "flightSpeed": 90,
      "outboundSpeed": 90,
      "inLabel": {
        "a": 0,
        "_default": 1,
        "_m": 1
      },
      "outLabel": {
        "a": 0,
        "_default": 1,
        "_m": 1
      },
      "speedAuto": 1,
      "retSpeedAuto": 1,
      "cumLabel": {
        "a": 0,
        "_default": 1,
        "_m": 1
      },
      "cumLabelRet": {
        "a": 0,
        "_default": 1,
        "_m": 1
      }
    },
    {
      "inboundAltitude": 6000,
      "outboundAltitude": 6000,
      "flightSpeed": 90,
      "outboundSpeed": 90,
      "inLabel": {
        "a": 0,
        "_default": 1,
        "_m": 1
      },
      "outLabel": {
        "a": 0,
        "_default": 1,
        "_m": 1
      },
      "speedAuto": 1,
      "retSpeedAuto": 1,
      "cumLabel": {
        "a": 0,
        "_default": 1,
        "_m": 1
      },
      "cumLabelRet": {
        "a": 0,
        "_default": 1,
        "_m": 1
      }
    },
    {
      "inboundAltitude": 6000,
      "outboundAltitude": 6000,
      "flightSpeed": 90,
      "outboundSpeed": 90,
      "inLabel": {
        "a": 0,
        "_default": 1,
        "_m": 1
      },
      "outLabel": {
        "a": 0,
        "_default": 1,
        "_m": 1
      },
      "speedAuto": 1,
      "retSpeedAuto": 1,
      "cumLabel": {
        "a": 0,
        "_default": 1,
        "_m": 1
      },
      "cumLabelRet": {
        "a": 0,
        "_default": 1,
        "_m": 1
      }
    }
  ],
  "notes": [],
  "title": "Herzliya - Rosh Pina (CVFR exercise)",
  "navlog": {
    "cruiseAltFt": 6000,
    "variationDeg": 4,
    "depElevFt": 100,
    "destElevFt": 900,
    "cas": {
      "climb": 70,
      "cruise": 90,
      "descent": 100
    },
    "rates": {
      "climbFpm": 800,
      "descentFpm": 1000
    },
    "fuel": {
      "climbGal": 7,
      "cruiseGph": 8
    },
    "met": [
      {
        "alt": 2000,
        "dir": 315,
        "kt": 17,
        "tempC": 10
      },
      {
        "alt": 3000,
        "dir": 290,
        "kt": 17,
        "tempC": 8
      },
      {
        "alt": 4000,
        "dir": 300,
        "kt": 20,
        "tempC": 6
      },
      {
        "alt": 5000,
        "dir": 300,
        "kt": 22,
        "tempC": 4
      },
      {
        "alt": 6000,
        "dir": 320,
        "kt": 25,
        "tempC": 2
      },
      {
        "alt": 7000,
        "dir": 325,
        "kt": 27,
        "tempC": 0
      }
    ],
    "deviation": [
      {
        "mh": 0,
        "ch": 358
      },
      {
        "mh": 30,
        "ch": 27
      },
      {
        "mh": 60,
        "ch": 59
      },
      {
        "mh": 90,
        "ch": 90
      },
      {
        "mh": 120,
        "ch": 122
      },
      {
        "mh": 150,
        "ch": 152
      },
      {
        "mh": 180,
        "ch": 180
      },
      {
        "mh": 210,
        "ch": 209
      },
      {
        "mh": 240,
        "ch": 241
      },
      {
        "mh": 270,
        "ch": 272
      },
      {
        "mh": 300,
        "ch": 300
      },
      {
        "mh": 330,
        "ch": 332
      }
    ]
  }
}

It is the sheet the arithmetic here was checked against, so what comes out should match the published answers cell for cell — 74.2 / 98.3 / 105.2 KCAS→TAS, pressure altitudes 4033 and 3450, TOC inside the first leg, 14.4 gallons at the bottom.

Exercises as files

An exercise is a file: a route, a met table, a compass card and a set of assumptions. It opens through the app's own Open, in Import/Export — one door, one place to look. A document carrying a navlog block is recognised there, and if it also validates as a route the route takes the ordinary route path first, so the plan lands complete with its legs and planned altitudes.

Save as a file writes the same shape back.

It is somebody else's JSON, so it is read as such: coordinates must be coordinates, the met table is capped at 60 levels and the card at 72 entries, names are clipped, and a file with neither a route nor a single assumption in it is refused with a toast rather than half-loaded. Replacing a drawn route is asked first — and saying no still lands the met table and the card.

The window

Dragged by its title bar and remembered where you put it (navaid.navlogPos), resizable by its corner, and it comes back after a reload or a language switch like every other chart window. Opening the flight plan closes it: one chart on screen.

It opens at the sheet's width rather than the screen's, and the sheet scrolls inside the window while the title and its ✕ stay put. The 23 columns scroll sideways on a narrow screen, with a shadow at whichever edge has more table past it — a phone's scrollbar only appears while it is moving, so without that the sheet simply looked cut off.

The figures line up on an edge rather than centred, and the columns are ruled into the three things they are: what you were given (to TAS), the wind triangle worked through it (to CH), and what comes out (from GS). Every abbreviated heading — TT, FF, Dev — names itself in a tooltip.

Tunables

Key Default What it does
featureNavLog true Offer the form at all. The gist can withdraw it
magneticVariationDeg -5 The variation the form starts from

For maintainers

The arithmetic is in docs/app/core.js — navLogRows() and the pure functions around it (tasFromCas, navLogSegmentAltFt, navLogMetRow, navLogDeviation, navLogHeadings), with no DOM and no storage. The window is docs/app/navlog.js. navaid.navlog holds only what was typed: everything else is re-derived, which is what lets an untouched elevation follow the route.

tests/navlog-golden.spec.js reproduces a published exercise cell by cell from tests/fixtures/navlog-herzliya-rosh-pina.json; navlog-rules.spec.js covers each rule and the cases a real route does not reach; navlog-view.spec.js drives the window.

NavAid · 🇮🇱 עברית

For pilots

For developers


🇮🇱 עברית

לטייסים

למפתחים


🌐 Live 🧪 Staging 📦 Repo 💡 Ideas / bugs

Clone this wiki locally