diff --git a/public/models/fleet/types.json b/public/models/fleet/types.json index 3492f58c9..d0a62f091 100644 --- a/public/models/fleet/types.json +++ b/public/models/fleet/types.json @@ -1149,6 +1149,7 @@ 70, 79 ], + "typical_len": 300, "rules": [ { "min_len": 70, @@ -1188,6 +1189,7 @@ 80, 89 ], + "typical_len": 250, "rules": [ { "name_re": "\\bLNG\\b|\\bGAS\\b", @@ -1214,6 +1216,7 @@ 60, 69 ], + "typical_len": 140, "rules": [ { "min_len": 70, @@ -1243,6 +1246,7 @@ 37, 37 ], + "typical_len": 19, "rules": [ { "min_len": 35, @@ -1273,6 +1277,7 @@ 30, 30 ], + "typical_len": 24, "rules": [ { "min_len": 15, @@ -1343,6 +1348,7 @@ 55, 55 ], + "typical_len": 40, "rules": [ { "min_len": 25, @@ -1358,6 +1364,7 @@ 40, 49 ], + "typical_len": 40, "rules": [ { "min_len": 20, @@ -1373,6 +1380,7 @@ 33, 33 ], + "typical_len": 100, "rules": [ { "min_len": 50, diff --git a/src/data/fleetModels.js b/src/data/fleetModels.js index 7c0204fdd..6a3749561 100644 --- a/src/data/fleetModels.js +++ b/src/data/fleetModels.js @@ -133,8 +133,9 @@ export function hullBeam(beam) { /** * The model for a vessel of AIS ship type `shipType` and the reported length * and beam (metres) and name: the first rule that matches in the first type - * range that has one, else `default_vessel`. Drawn at the reported length, as - * built when it is unknown. + * range that has one, else `default_vessel`. Drawn at the reported length; + * when it is unknown (most live feeds send none), chosen and drawn at the + * range's `typical_len`, else as built. * @param {object} types Parsed `types.json`. * @param {number} [shipType] * @param {number} [length] @@ -145,13 +146,14 @@ export function hullBeam(beam) { export function resolveVessel(types, shipType, length, beam, name) { const L = hullLength(length) ?? 0; const B = hullBeam(beam) ?? 0; - const drawn = L > 0 ? L : null; if (shipType !== undefined && shipType !== null) { for (const group of types.vessels) { if (!(shipType >= group.types[0] && shipType <= group.types[1])) continue; + const len = L > 0 ? L : (group.typical_len ?? 0); + const drawn = len > 0 ? len : null; for (const rule of group.rules) { - if (rule.min_len && L < rule.min_len) continue; - if (rule.max_len && L > rule.max_len) continue; + if (rule.min_len && len < rule.min_len) continue; + if (rule.max_len && len > rule.max_len) continue; if (rule.name_re && !new RegExp(rule.name_re).test(name ?? '')) continue; if (rule.max_bl && !(B > 0 && L > 0 && B / L <= rule.max_bl)) continue; @@ -159,7 +161,7 @@ export function resolveVessel(types, shipType, length, beam, name) { } } } - return { model: types.default_vessel, length: drawn }; + return { model: types.default_vessel, length: L > 0 ? L : null }; } /** diff --git a/src/data/fleetModels.test.mjs b/src/data/fleetModels.test.mjs index 9eb484a6a..f0387e65d 100644 --- a/src/data/fleetModels.test.mjs +++ b/src/data/fleetModels.test.mjs @@ -57,6 +57,7 @@ const sliceTypes = { vessels: [ { types: [70, 79], + typical_len: 300, rules: [ { min_len: 70, max_len: 140, max_bl: 0.12, model: 'inland_cargo' }, { @@ -133,6 +134,29 @@ test('vessels: the first matching rule of the first type range with one, else th }); }); +test("vessels of unknown length are chosen and drawn at their range's typical length", () => { + assert.deepEqual(resolveVessel(sliceTypes, 70), { + model: 'container_ship', + length: 300, + }); + assert.deepEqual(resolveVessel(sliceTypes, 70, 511, 126), { + model: 'container_ship', + length: 300, + }); + assert.deepEqual(resolveVessel(sliceTypes, 70, 90, 15), { + model: 'general_cargo', + length: 90, + }); + assert.deepEqual(resolveVessel(sliceTypes, 31), { + model: 'tug', + length: null, + }); + // Every passenger, cargo and tanker range of the set has one. + for (const code of [60, 70, 80]) { + assert.ok(resolveVessel(types, code).length > 100, String(code)); + } +}); + test("vessels: AIS's out-of-range sizes (511 m long, 126 m abeam) are unknown, drawn as built", () => { assert.deepEqual(resolveVessel(sliceTypes, 52, 511), { model: 'tug',