diff --git a/changelog.d/1491-gnss-source-window.md b/changelog.d/1491-gnss-source-window.md new file mode 100644 index 000000000..108aa50b2 --- /dev/null +++ b/changelog.d/1491-gnss-source-window.md @@ -0,0 +1,3 @@ +- Let a GNSS integrity source set the layer's evidence window (`windowMs`). A + missing or invalid value keeps the 30-minute default, and the layer row now + names the window in use instead of a fixed "30 min" (samehkhamis, #1491). diff --git a/src/app/constructCatalog.js b/src/app/constructCatalog.js index 59edc02ad..198f91dff 100644 --- a/src/app/constructCatalog.js +++ b/src/app/constructCatalog.js @@ -126,6 +126,7 @@ export function createApplicationCatalog({ }), createApplicationGnssIntegrity({ source: sources['gnss-integrity'], + windowMs: sources['gnss-integrity'].windowMs, }), createApplicationGhostMaps({ source: sources['s2-ghostmaps'] }), createApplicationAlpr({ surface, source: sources.alpr }), diff --git a/src/app/constructCatalog.test.mjs b/src/app/constructCatalog.test.mjs index eb9e88d31..5593d6fc0 100644 --- a/src/app/constructCatalog.test.mjs +++ b/src/app/constructCatalog.test.mjs @@ -193,3 +193,42 @@ test('Mapillary can be omitted or replaced without changing catalog membership', /Mapillary source/, ); }); + +test('a GNSS integrity source can set the layer evidence window', (t) => { + const lifetime = new AbortController(); + t.after(() => lifetime.abort()); + const gnssLayer = (descriptor) => { + const sources = fixtureSources([], []); + sources['gnss-integrity'] = { + getSnapshot: async () => ({}), + ...descriptor, + }; + return createApplicationCatalog({ + sources, + signal: lifetime.signal, + surface: fixtureSurface(lifetime.signal), + }).get('gnss-integrity'); + }; + const shows = (layer, minutes) => { + assert.equal(layer.provenance.window.minutes, minutes); + const { info, legend } = layer.getRowControls(); + assert.match(info, new RegExp(`visited views over ${minutes} min`)); + assert.match(legend[0].blurb, new RegExp(`over ${minutes} minutes`)); + }; + shows(gnssLayer({ windowMs: 65 * 60_000 }), 65); + shows(gnssLayer({}), 30); + for (const windowMs of [ + undefined, + null, + NaN, + Infinity, + 0, + -60_000, + '3600000', + ]) + assert.equal( + gnssLayer({ windowMs }).provenance.window.minutes, + 30, + `${String(windowMs)} falls back to the default`, + ); +}); diff --git a/src/i18n/locales/de.json b/src/i18n/locales/de.json index 938fb20bc..b0d9ca463 100644 --- a/src/i18n/locales/de.json +++ b/src/i18n/locales/de.json @@ -1458,7 +1458,7 @@ "Low-accuracy score 2–10%": "Ungenauigkeitswert 2–10 %", "Low-accuracy score > 10%": "Ungenauigkeitswert > 10 %", "GNSS integrity source unavailable": "GNSS-Integritätsquelle nicht verfügbar", - "Regional sample · 250 nm radius · cells from visited views over 30 min": "Regionale Stichprobe · Radius 250 NM · Zellen aus besuchten Ansichten über 30 min", + "Regional sample · 250 nm radius · cells from visited views over {minutes} min": "Regionale Stichprobe · Radius 250 NM · Zellen aus besuchten Ansichten über {minutes} min", "Retrying automatically at {time} UTC": "Automatischer neuer Versuch um {time} UTC", "Retrying automatically after provider cooldown": "Automatischer neuer Versuch nach der Sperrzeit des Anbieters", "Updates automatically after pan or zoom and once a minute. The public feed covers a 250-nautical-mile circle around the view centre; zooming out does not make it a worldwide snapshot. Blank cells are outside sampled coverage or have insufficient observations.": "Aktualisiert sich nach Schwenken oder Zoomen und einmal pro Minute automatisch. Der öffentliche Feed deckt einen Kreis von 250 Seemeilen um die Bildmitte ab; Herauszoomen macht daraus keinen weltweiten Snapshot. Leere Zellen liegen außerhalb der Stichprobe oder haben zu wenige Beobachtungen.", diff --git a/src/i18n/locales/es.json b/src/i18n/locales/es.json index 5b893a0cb..643937da0 100644 --- a/src/i18n/locales/es.json +++ b/src/i18n/locales/es.json @@ -2158,7 +2158,7 @@ "Low-accuracy score 2–10%": "Índice de baja precisión 2–10%", "Low-accuracy score > 10%": "Índice de baja precisión > 10%", "GNSS integrity source unavailable": "Fuente de integridad GNSS no disponible", - "Regional sample · 250 nm radius · cells from visited views over 30 min": "Muestra regional · radio de 250 NM · celdas de las vistas visitadas en 30 min", + "Regional sample · 250 nm radius · cells from visited views over {minutes} min": "Muestra regional · radio de 250 NM · celdas de las vistas visitadas en {minutes} min", "Retrying automatically at {time} UTC": "Reintento automático a las {time} UTC", "Retrying automatically after provider cooldown": "Reintento automático tras la pausa del proveedor", "Updates automatically after pan or zoom and once a minute. The public feed covers a 250-nautical-mile circle around the view centre; zooming out does not make it a worldwide snapshot. Blank cells are outside sampled coverage or have insufficient observations.": "Se actualiza automáticamente al desplazar o hacer zoom y una vez por minuto. El feed público cubre un círculo de 250 millas náuticas alrededor del centro de la vista; alejarte no lo convierte en una instantánea mundial. Las celdas vacías están fuera de la cobertura muestreada o no tienen observaciones suficientes.", diff --git a/src/i18n/locales/fr.json b/src/i18n/locales/fr.json index 895b121eb..18a14b1db 100644 --- a/src/i18n/locales/fr.json +++ b/src/i18n/locales/fr.json @@ -771,7 +771,7 @@ "Low-accuracy score 2–10%": "Indice de faible précision 2–10 %", "Low-accuracy score > 10%": "Indice de faible précision > 10 %", "GNSS integrity source unavailable": "Source d'intégrité GNSS indisponible", - "Regional sample · 250 nm radius · cells from visited views over 30 min": "Échantillon régional · rayon de 250 NM · cellules des vues visitées sur 30 min", + "Regional sample · 250 nm radius · cells from visited views over {minutes} min": "Échantillon régional · rayon de 250 NM · cellules des vues visitées sur {minutes} min", "Retrying automatically at {time} UTC": "Nouvel essai automatique à {time} UTC", "Retrying automatically after provider cooldown": "Nouvel essai automatique après la pause du fournisseur", "Updates automatically after pan or zoom and once a minute. The public feed covers a 250-nautical-mile circle around the view centre; zooming out does not make it a worldwide snapshot. Blank cells are outside sampled coverage or have insufficient observations.": "Mise à jour automatique après un déplacement ou un zoom, et une fois par minute. Le flux public couvre un cercle de 250 milles nautiques autour du centre de la vue ; dézoomer n'en fait pas un instantané mondial. Les cellules vides sont hors de la couverture échantillonnée ou manquent d'observations.", diff --git a/src/i18n/locales/it.json b/src/i18n/locales/it.json index ccce863d4..bcb6d73d3 100644 --- a/src/i18n/locales/it.json +++ b/src/i18n/locales/it.json @@ -771,7 +771,7 @@ "Low-accuracy score 2–10%": "Indice di bassa precisione 2–10%", "Low-accuracy score > 10%": "Indice di bassa precisione > 10%", "GNSS integrity source unavailable": "Fonte di integrità GNSS non disponibile", - "Regional sample · 250 nm radius · cells from visited views over 30 min": "Campione regionale · raggio di 250 NM · celle dalle viste visitate negli ultimi 30 min", + "Regional sample · 250 nm radius · cells from visited views over {minutes} min": "Campione regionale · raggio di 250 NM · celle dalle viste visitate negli ultimi {minutes} min", "Retrying automatically at {time} UTC": "Nuovo tentativo automatico alle {time} UTC", "Retrying automatically after provider cooldown": "Nuovo tentativo automatico dopo la pausa del provider", "Updates automatically after pan or zoom and once a minute. The public feed covers a 250-nautical-mile circle around the view centre; zooming out does not make it a worldwide snapshot. Blank cells are outside sampled coverage or have insufficient observations.": "Si aggiorna automaticamente dopo uno spostamento o uno zoom e una volta al minuto. Il feed pubblico copre un cerchio di 250 miglia nautiche attorno al centro della vista; allontanare lo zoom non lo rende un'istantanea mondiale. Le celle vuote sono fuori dalla copertura campionata o hanno osservazioni insufficienti.", diff --git a/src/i18n/locales/ja.json b/src/i18n/locales/ja.json index edd9c079f..ce0a26bfe 100644 --- a/src/i18n/locales/ja.json +++ b/src/i18n/locales/ja.json @@ -749,7 +749,7 @@ "Low-accuracy score 2–10%": "低精度スコア 2–10%", "Low-accuracy score > 10%": "低精度スコア > 10%", "GNSS integrity source unavailable": "GNSSインテグリティのソースを利用できません", - "Regional sample · 250 nm radius · cells from visited views over 30 min": "地域サンプル · 半径250海里 · 30分以内に表示したビューのセル", + "Regional sample · 250 nm radius · cells from visited views over {minutes} min": "地域サンプル · 半径250海里 · {minutes}分以内に表示したビューのセル", "Retrying automatically at {time} UTC": "{time} UTCに自動で再試行します", "Retrying automatically after provider cooldown": "プロバイダーのクールダウン後に自動で再試行します", "Updates automatically after pan or zoom and once a minute. The public feed covers a 250-nautical-mile circle around the view centre; zooming out does not make it a worldwide snapshot. Blank cells are outside sampled coverage or have insufficient observations.": "パンやズームの後、および1分ごとに自動で更新されます。公開フィードは表示中心から半径250海里の円をカバーします。ズームアウトしても全世界のスナップショットにはなりません。空白のセルは、サンプリング範囲外か観測が不十分です。", diff --git a/src/i18n/locales/pt.json b/src/i18n/locales/pt.json index c3f4bcf90..0a99f7e8e 100644 --- a/src/i18n/locales/pt.json +++ b/src/i18n/locales/pt.json @@ -769,7 +769,7 @@ "Low-accuracy score 2–10%": "Índice de baixa precisão 2–10%", "Low-accuracy score > 10%": "Índice de baixa precisão > 10%", "GNSS integrity source unavailable": "Fonte de integridade GNSS indisponível", - "Regional sample · 250 nm radius · cells from visited views over 30 min": "Amostra regional · raio de 250 NM · células das vistas visitadas em 30 min", + "Regional sample · 250 nm radius · cells from visited views over {minutes} min": "Amostra regional · raio de 250 NM · células das vistas visitadas em {minutes} min", "Retrying automatically at {time} UTC": "Nova tentativa automática às {time} UTC", "Retrying automatically after provider cooldown": "Nova tentativa automática após a pausa do provedor", "Updates automatically after pan or zoom and once a minute. The public feed covers a 250-nautical-mile circle around the view centre; zooming out does not make it a worldwide snapshot. Blank cells are outside sampled coverage or have insufficient observations.": "Atualiza automaticamente após mover ou dar zoom e uma vez por minuto. O feed público cobre um círculo de 250 milhas náuticas ao redor do centro da vista; afastar o zoom não o transforma em um snapshot mundial. Células vazias estão fora da cobertura amostrada ou não têm observações suficientes.", diff --git a/src/i18n/locales/ru.json b/src/i18n/locales/ru.json index fe8338c3f..5a64abfb9 100644 --- a/src/i18n/locales/ru.json +++ b/src/i18n/locales/ru.json @@ -2192,7 +2192,7 @@ "Low-accuracy score 2–10%": "Доля неточных 2–10%", "Low-accuracy score > 10%": "Доля неточных > 10%", "GNSS integrity source unavailable": "Источник целостности GNSS недоступен", - "Regional sample · 250 nm radius · cells from visited views over 30 min": "Региональная выборка · радиус 250 мор. миль · ячейки из просмотренных видов за 30 мин", + "Regional sample · 250 nm radius · cells from visited views over {minutes} min": "Региональная выборка · радиус 250 мор. миль · ячейки из просмотренных видов за {minutes} мин", "Retrying automatically at {time} UTC": "Автоматический повтор в {time} UTC", "Retrying automatically after provider cooldown": "Автоматический повтор после паузы провайдера", "Updates automatically after pan or zoom and once a minute. The public feed covers a 250-nautical-mile circle around the view centre; zooming out does not make it a worldwide snapshot. Blank cells are outside sampled coverage or have insufficient observations.": "Обновляется автоматически после сдвига или масштабирования карты и раз в минуту. Публичный поток данных охватывает круг радиусом 250 морских миль вокруг центра вида; при отдалении карта не становится снимком всего мира. Пустые ячейки — вне охвата выборки или с недостаточным числом наблюдений.", diff --git a/src/i18n/locales/uk.json b/src/i18n/locales/uk.json index 37e55b961..282c5990a 100644 --- a/src/i18n/locales/uk.json +++ b/src/i18n/locales/uk.json @@ -781,7 +781,7 @@ "Low-accuracy score 2–10%": "Показник неточності 2–10%", "Low-accuracy score > 10%": "Показник неточності > 10%", "GNSS integrity source unavailable": "Джерело даних про цілісність GNSS недоступне", - "Regional sample · 250 nm radius · cells from visited views over 30 min": "Регіональна вибірка · радіус 250 мор. миль · комірки з переглянутих ділянок карти за 30 хв", + "Regional sample · 250 nm radius · cells from visited views over {minutes} min": "Регіональна вибірка · радіус 250 мор. миль · комірки з переглянутих ділянок карти за {minutes} хв", "Retrying automatically at {time} UTC": "Автоматичний повтор о {time} UTC", "Retrying automatically after provider cooldown": "Автоматичний повтор після паузи постачальника", "Updates automatically after pan or zoom and once a minute. The public feed covers a 250-nautical-mile circle around the view centre; zooming out does not make it a worldwide snapshot. Blank cells are outside sampled coverage or have insufficient observations.": "Оновлюється автоматично після зсуву чи масштабування карти та раз на хвилину. Публічний потік даних охоплює коло радіусом 250 морських миль навколо центру карти; у разі віддалення карта не стає знімком усього світу. Порожні комірки — поза охопленням вибірки або з недостатньою кількістю спостережень.", diff --git a/src/i18n/locales/zh-CN.json b/src/i18n/locales/zh-CN.json index 8dc28b308..318c862bb 100644 --- a/src/i18n/locales/zh-CN.json +++ b/src/i18n/locales/zh-CN.json @@ -726,7 +726,7 @@ "Low-accuracy score 2–10%": "低精度评分 2–10%", "Low-accuracy score > 10%": "低精度评分 > 10%", "GNSS integrity source unavailable": "GNSS 完好性数据源不可用", - "Regional sample · 250 nm radius · cells from visited views over 30 min": "区域采样 · 半径 250 海里 · 30 分钟内已访问视图中的网格", + "Regional sample · 250 nm radius · cells from visited views over {minutes} min": "区域采样 · 半径 250 海里 · {minutes} 分钟内已访问视图中的网格", "Retrying automatically at {time} UTC": "将于 {time} UTC 自动重试", "Retrying automatically after provider cooldown": "服务商冷却结束后自动重试", "Updates automatically after pan or zoom and once a minute. The public feed covers a 250-nautical-mile circle around the view centre; zooming out does not make it a worldwide snapshot. Blank cells are outside sampled coverage or have insufficient observations.": "平移或缩放后以及每分钟自动更新。公共数据源覆盖以视野中心为圆心、半径 250 海里的范围;缩小地图并不会使其成为全球快照。空白网格位于采样覆盖范围之外,或观测数据不足。", diff --git a/src/i18n/locales/zh-Hant.json b/src/i18n/locales/zh-Hant.json index c82131223..d136fcd4d 100644 --- a/src/i18n/locales/zh-Hant.json +++ b/src/i18n/locales/zh-Hant.json @@ -721,7 +721,7 @@ "Low-accuracy score 2–10%": "低精度評分 2–10%", "Low-accuracy score > 10%": "低精度評分 > 10%", "GNSS integrity source unavailable": "GNSS 完好性數據源不可用", - "Regional sample · 250 nm radius · cells from visited views over 30 min": "區域採樣 · 半徑 250 海里 · 30 分鐘內已訪問視圖中的網格", + "Regional sample · 250 nm radius · cells from visited views over {minutes} min": "區域採樣 · 半徑 250 海里 · {minutes} 分鐘內已訪問視圖中的網格", "Retrying automatically at {time} UTC": "將於 {time} UTC 自動重試", "Retrying automatically after provider cooldown": "服務商冷卻結束後自動重試", "Updates automatically after pan or zoom and once a minute. The public feed covers a 250-nautical-mile circle around the view centre; zooming out does not make it a worldwide snapshot. Blank cells are outside sampled coverage or have insufficient observations.": "平移或縮放後以及每分鐘自動更新。公共數據源覆蓋以視野中心為圓心、半徑 250 海里的範圍;縮小地圖並不會使其成為全球快照。空白網格位於採樣覆蓋範圍之外,或觀測數據不足。", diff --git a/src/layers/gnss/index.js b/src/layers/gnss/index.js index f3a4102f8..ba0e9ff5d 100644 --- a/src/layers/gnss/index.js +++ b/src/layers/gnss/index.js @@ -70,10 +70,16 @@ export function createGnssIntegrityLayer({ viewAnchor = defaultGnssViewAnchor, now = () => Date.now(), monotonicNow = () => performance.now(), - windowMs = GNSS_WINDOW_MS, + windowMs: requestedWindowMs, } = {}) { if (typeof source?.getSnapshot !== 'function') throw new TypeError('GNSS integrity layer requires a snapshot source'); + // A source may set its own evidence window; anything but a positive finite + // duration keeps the default. + const windowMs = + Number.isFinite(requestedWindowMs) && requestedWindowMs > 0 + ? requestedWindowMs + : GNSS_WINDOW_MS; let _viewer = null; let _primitives = null; let _request = null; @@ -375,7 +381,8 @@ export function createGnssIntegrityLayer({ chips: [], info: t( - 'Regional sample · 250 nm radius · cells from visited views over 30 min', + 'Regional sample · 250 nm radius · cells from visited views over {minutes} min', + { minutes: formatNumber(provenance.window.minutes) }, ) + (_lastError && _retryAt ? ` · ${translateFeedError(_lastError)} · ${