From 266825eddbdef45ead62cffc540e3e5879112895 Mon Sep 17 00:00:00 2001 From: Sameh Khamis Date: Tue, 3 Nov 2026 14:36:57 -0800 Subject: [PATCH] fix(drone-airspace): show FAA times-of-use line breaks as separators FAA marks line breaks in TIMESOFUSE with a literal %, which 56 live restricted-area cards showed verbatim ("SAT - SUN; %OTHER TIMES BY NOTAM"). Read each marker as a '; ' separator, absorbing a semicolon already before it. --- src/layers/droneAirspace/records.js | 10 +++++++- src/layers/droneAirspace/records.test.mjs | 29 +++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/layers/droneAirspace/records.js b/src/layers/droneAirspace/records.js index 7d9a95b2c..d43b3c23b 100644 --- a/src/layers/droneAirspace/records.js +++ b/src/layers/droneAirspace/records.js @@ -18,6 +18,14 @@ const textOrNull = (value) => { return text ? text : null; }; +/** FAA marks line breaks in TIMESOFUSE with `%`; read them as `; ` separators. */ +const timesOfUseOrNull = (value) => + textOrNull( + typeof value === 'string' + ? value.replace(/\s*;?\s*%\s*/g, '; ').replace(/^[;\s]+|[;\s]+$/g, '') + : value, + ); + function validRing(ring) { if (!Array.isArray(ring) || ring.length < 4) return false; for (const position of ring) { @@ -116,7 +124,7 @@ export function normalizeDroneAirspaceSnapshot(geojson) { properties.UPPER_UOM, properties.UPPER_CODE, ), - timesOfUse: textOrNull(properties.TIMESOFUSE), + timesOfUse: timesOfUseOrNull(properties.TIMESOFUSE), agency: textOrNull(properties.CONT_AGENT), city: textOrNull(properties.CITY), state: textOrNull(properties.STATE), diff --git a/src/layers/droneAirspace/records.test.mjs b/src/layers/droneAirspace/records.test.mjs index 1c509b5de..be108c5ea 100644 --- a/src/layers/droneAirspace/records.test.mjs +++ b/src/layers/droneAirspace/records.test.mjs @@ -133,3 +133,32 @@ test('duplicate ids and a missing collection reject or skip as documented', () = assert.equal(droneAirspaceTypeLabel('R'), 'Restricted'); assert.equal(droneAirspaceTypeLabel('MOA'), null); }); + +test("FAA's % line-break markers in times of use read as separators", () => { + const timesOfUse = (TIMESOFUSE) => + normalizeDroneAirspaceSnapshot({ + features: [ + feature({ + properties: { + NAME: 'R-2508', + TYPE_CODE: 'R', + OBJECTID: 400, + TIMESOFUSE, + }, + }), + ], + })[0].timesOfUse; + // The shape 56 live records carry: a marker after the semicolon. + assert.equal( + timesOfUse( + '0700 - 2200 MON - FRI; 0800 - 1600 SAT - SUN; %OTHER TIMES BY NOTAM', + ), + '0700 - 2200 MON - FRI; 0800 - 1600 SAT - SUN; OTHER TIMES BY NOTAM', + ); + assert.equal( + timesOfUse('SR - SS DAILY %OTHER TIMES BY NOTAM'), + 'SR - SS DAILY; OTHER TIMES BY NOTAM', + ); + assert.equal(timesOfUse('%CONTINUOUS%'), 'CONTINUOUS'); + assert.equal(timesOfUse(' % '), null); +});