mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-15 07:42:23 +00:00
Merge branch 'main' into fix/e-cctv-heading-north
This commit is contained in:
commit
0e942de38c
10 files changed
+80
-8
No files matched your search
@@ -0,0 +1,3 @@
|
|||||||
|
- Show a vessel course of 359.5° or more as 0°. Vessel cards and the AIS HUD
|
||||||
|
rounded the course without wrapping, so a ship with no reported heading on
|
||||||
|
a 359.7° course read "360°" and "HDG: 360DEG" (Aditya, @Rohilalala, #1432).
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
- Show an ALPR camera's mapped bearing of 359.5° or more as DIRECTION 0°
|
||||||
|
rather than DIRECTION 360° (Aditya, @Rohilalala, #1451).
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
- Show a transit vehicle's reported bearing of 359.5° or more as 0°. The
|
||||||
|
selected-vehicle card rounded it without wrapping, so a bus heading 359.6°
|
||||||
|
read "reported hdg 360°" (Aditya, @Rohilalala, #1452).
|
||||||
@@ -557,7 +557,9 @@ those reports arrive as 102.3 knots and 360 degrees. Both are stored as unknown,
|
|||||||
and vessel cards, the HUD and analyst queries show a missing measurement rather
|
and vessel cards, the HUD and analyst queries show a missing measurement rather
|
||||||
than a reading. Heading keeps its existing separate check for its own sentinel.
|
than a reading. Heading keeps its existing separate check for its own sentinel.
|
||||||
Genuine readings are unchanged, including a stopped vessel's zero and the
|
Genuine readings are unchanged, including a stopped vessel's zero and the
|
||||||
highest speed and course the fields can encode.
|
highest speed and course the fields can encode. Vessel cards and the HUD wrap
|
||||||
|
the direction after rounding to whole degrees, so a course of 359.5° or more
|
||||||
|
reads `0°` (`0DEG` in the HUD) rather than `360°`.
|
||||||
|
|
||||||
For an already tracked vessel, AISStream static messages update name, type,
|
For an already tracked vessel, AISStream static messages update name, type,
|
||||||
destination and IMO without changing its position, motion, fix timestamps,
|
destination and IMO without changing its position, motion, fix timestamps,
|
||||||
@@ -958,7 +960,9 @@ change. Once the camera is still and tiles have settled, each overlay anchor is
|
|||||||
replaced once by the rendered surface under it (two depth samples per paint,
|
replaced once by the rendered surface under it (two depth samples per paint,
|
||||||
accepted only within -30/+40 m of the floor cell), so badges do not float or
|
accepted only within -30/+40 m of the floor cell), so badges do not float or
|
||||||
sink on slopes; wedge corners follow their own floor cells instead of staying
|
sink on slopes; wedge corners follow their own floor cells instead of staying
|
||||||
level with the apex. Unknown bearings never acquire a wedge. The row reads
|
level with the apex. Unknown bearings never acquire a wedge. The selected
|
||||||
|
card's `DIRECTION` wraps after rounding to whole degrees, so a mapped bearing
|
||||||
|
of 359.5° or more reads `0°` rather than `360°`. The row reads
|
||||||
`N nearby · M on screen`; when every loaded camera is outside the view it adds
|
`N nearby · M on screen`; when every loaded camera is outside the view it adds
|
||||||
"None on screen — nearby cameras are outside the view" instead of looking
|
"None on screen — nearby cameras are outside the view" instead of looking
|
||||||
unloaded. `scripts/qa-alpr-journey.mjs` drives an Austin journey with ALPR off,
|
unloaded. `scripts/qa-alpr-journey.mjs` drives an Austin journey with ALPR off,
|
||||||
@@ -4488,7 +4492,9 @@ to 0.45 at two minutes, 0.20 at ten minutes and 0.08 at fifteen minutes.
|
|||||||
display delay and newest-report age, reported speed/stop, vehicle ID, and
|
display delay and newest-report age, reported speed/stop, vehicle ID, and
|
||||||
available trail duration. Report age is rounded to whole seconds and stays in seconds while that rounded
|
available trail duration. Report age is rounded to whole seconds and stays in seconds while that rounded
|
||||||
count is below 90; otherwise it shows completed minutes calculated from the
|
count is below 90; otherwise it shows completed minutes calculated from the
|
||||||
rounded second count. Its anchor follows the rendered marker; text is
|
rounded second count. The reported heading wraps after rounding to whole
|
||||||
|
degrees, so a bearing of 359.5° or more reads `hdg 0°` rather than `hdg 360°`.
|
||||||
|
Its anchor follows the rendered marker; text is
|
||||||
checked at 4 Hz and published when it changes. DETECT labels retain route,
|
checked at 4 Hz and published when it changes. DETECT labels retain route,
|
||||||
mode and motion, with the selected label suppressed beside its fuller card.
|
mode and motion, with the selected label suppressed beside its fuller card.
|
||||||
Candidate membership changes on revisions, coalesced at 4 Hz; paints read
|
Candidate membership changes on revisions, coalesced at 4 Hz; paints read
|
||||||
|
|||||||
@@ -1321,6 +1321,27 @@ test('buildVesselCard: heading falls back to course; missing parts are omitted',
|
|||||||
assert.deepEqual(bare.details, []);
|
assert.deepEqual(bare.details, []);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('vessel cards read a course that rounds to north as 0°, not 360°', () => {
|
||||||
|
const card = (course) => buildVesselCard(makeRecord({ heading: null, course })).details[0];
|
||||||
|
assert.equal(card(359.4), 'CONTAINER SHIP · 14.5KT · 359°');
|
||||||
|
assert.equal(card(359.7), 'CONTAINER SHIP · 14.5KT · 0°');
|
||||||
|
assert.equal(
|
||||||
|
buildSelectedVesselCard(makeRecord({ heading: null, course: 359.7 })).details[0],
|
||||||
|
'CONTAINER SHIP · 14.5KT · 0°',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the vessel HUD reads a course that rounds to north as HDG 0DEG', () => {
|
||||||
|
const harness = installWireHarness(undefined, { selectedRecord: null });
|
||||||
|
try {
|
||||||
|
Object.assign(harness.record, { heading: null, course: 359.7 });
|
||||||
|
assert.equal(aisLiveVesselsLayer.selectById(harness.record.mmsi), true);
|
||||||
|
assert.match(harness.hud.readout.text, /HDG: 0DEG/);
|
||||||
|
} finally {
|
||||||
|
harness.cleanup();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('buildVesselCard: unnamed vessels title as MMSI; long names truncate', () => {
|
test('buildVesselCard: unnamed vessels title as MMSI; long names truncate', () => {
|
||||||
const unnamed = buildVesselCard(makeRecord({ name: 'VESSEL' }));
|
const unnamed = buildVesselCard(makeRecord({ name: 'VESSEL' }));
|
||||||
assert.equal(unnamed.title, 'MMSI 353136000');
|
assert.equal(unnamed.title, 'MMSI 353136000');
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import test from 'node:test';
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import * as Cesium from 'cesium';
|
import * as Cesium from 'cesium';
|
||||||
import { createAlprOverlay } from './overlay.js';
|
import { createAlprOverlay } from './overlay.js';
|
||||||
|
import { normalizeAlprNode } from './records.js';
|
||||||
import {
|
import {
|
||||||
directionWedgePositions,
|
directionWedgePositions,
|
||||||
alprLabelDetails,
|
alprLabelDetails,
|
||||||
@@ -218,6 +219,23 @@ test('adapter labels never claim a custom source is public OSM data', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('a mapped bearing that rounds up to a whole turn reads 0°, not 360°', () => {
|
||||||
|
const direction = (tag) =>
|
||||||
|
alprLabelDetails(
|
||||||
|
normalizeAlprNode({
|
||||||
|
type: 'node',
|
||||||
|
id: 1,
|
||||||
|
lat: 30,
|
||||||
|
lon: -97,
|
||||||
|
tags: { 'surveillance:type': 'ALPR', 'camera:direction': tag },
|
||||||
|
}),
|
||||||
|
{ label: 'Custom directory' },
|
||||||
|
).find((line) => line.startsWith('DIRECTION'));
|
||||||
|
assert.equal(direction('359.6'), 'DIRECTION 0°');
|
||||||
|
assert.equal(direction('359.4'), 'DIRECTION 359°');
|
||||||
|
assert.equal(direction('0.4'), 'DIRECTION 0°');
|
||||||
|
});
|
||||||
|
|
||||||
test('overlay glyphs and native badges shrink alike with distance', () => {
|
test('overlay glyphs and native badges shrink alike with distance', () => {
|
||||||
assert.equal(markerScale(100), 1.15);
|
assert.equal(markerScale(100), 1.15);
|
||||||
assert.equal(markerScale(40_000), 0.45);
|
assert.equal(markerScale(40_000), 0.45);
|
||||||
|
|||||||
@@ -49,8 +49,9 @@ export function alprLabelDetails(record, source) {
|
|||||||
const details = [
|
const details = [
|
||||||
sourceName === 'OpenStreetMap' ? 'OSM MAPPED' : `Source: ${sourceName}`,
|
sourceName === 'OpenStreetMap' ? 'OSM MAPPED' : `Source: ${sourceName}`,
|
||||||
];
|
];
|
||||||
|
// Wrap after rounding: 359.6° rounds to 360°, which is north.
|
||||||
if (Number.isFinite(record.directionDeg))
|
if (Number.isFinite(record.directionDeg))
|
||||||
details.push(`DIRECTION ${Math.round(record.directionDeg)}°`);
|
details.push(`DIRECTION ${Math.round(record.directionDeg) % 360}°`);
|
||||||
const equipment = [
|
const equipment = [
|
||||||
...new Set(
|
...new Set(
|
||||||
[record.manufacturer, record.operator, record.cameraType]
|
[record.manufacturer, record.operator, record.cameraType]
|
||||||
|
|||||||
@@ -405,9 +405,12 @@ export function buildTransitSelectionCopy(
|
|||||||
reportedMotion.push(
|
reportedMotion.push(
|
||||||
t('reported {speed} km/h', { speed: Math.round(record.speedMps * 3.6) }),
|
t('reported {speed} km/h', { speed: Math.round(record.speedMps * 3.6) }),
|
||||||
);
|
);
|
||||||
|
// 359.5° and above rounds to north; modulo keeps it from printing 360°.
|
||||||
if (Number.isFinite(record.bearing))
|
if (Number.isFinite(record.bearing))
|
||||||
reportedMotion.push(
|
reportedMotion.push(
|
||||||
t('reported hdg {heading}°', { heading: Math.round(record.bearing) }),
|
t('reported hdg {heading}°', {
|
||||||
|
heading: Math.round(record.bearing) % 360,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
// The operator's own claim, kept as a REPORT rather than as the live state.
|
// The operator's own claim, kept as a REPORT rather than as the live state.
|
||||||
// It describes the moment of the report, which the display may not have
|
// It describes the moment of the report, which the display may not have
|
||||||
|
|||||||
@@ -43,6 +43,18 @@ test('the selection card calls a Toronto 501 a streetcar', () => {
|
|||||||
assert.doesNotMatch(copy.details.join(' | '), /Tram/);
|
assert.doesNotMatch(copy.details.join(' | '), /Tram/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('the selection card reads a bearing that rounds to north as hdg 0°', () => {
|
||||||
|
const hdg = (bearing) =>
|
||||||
|
buildTransitSelectionCopy(
|
||||||
|
ttc(),
|
||||||
|
{ id: '4400', routeId: '501', timestamp: 0, bearing },
|
||||||
|
'tram',
|
||||||
|
0,
|
||||||
|
).details.find((line) => line.includes('hdg'));
|
||||||
|
assert.equal(hdg(359.4), 'reported hdg 359°');
|
||||||
|
assert.equal(hdg(359.6), 'reported hdg 0°');
|
||||||
|
});
|
||||||
|
|
||||||
test('detection labels carry the operator vocabulary within the field width', () => {
|
test('detection labels carry the operator vocabulary within the field width', () => {
|
||||||
const klass = transitDetectionClass('tram', ttc());
|
const klass = transitDetectionClass('tram', ttc());
|
||||||
assert.equal(klass, 'STREETCAR TTC');
|
assert.equal(klass, 'STREETCAR TTC');
|
||||||
|
|||||||
@@ -55,7 +55,10 @@ export function createCards({
|
|||||||
if (record.speed !== null && record.speed !== undefined)
|
if (record.speed !== null && record.speed !== undefined)
|
||||||
parts.push(formatSpeed(record.speed));
|
parts.push(formatSpeed(record.speed));
|
||||||
const direction = record.heading ?? record.course;
|
const direction = record.heading ?? record.course;
|
||||||
if (Number.isFinite(direction)) parts.push(`${Math.round(direction)}°`);
|
// A course of 359.5° and above rounds to north; modulo keeps it from
|
||||||
|
// printing 360°. The selected card and the HUD wrap the same way.
|
||||||
|
if (Number.isFinite(direction))
|
||||||
|
parts.push(`${Math.round(direction) % 360}°`);
|
||||||
return {
|
return {
|
||||||
id: vesselOverlayEntryId(record),
|
id: vesselOverlayEntryId(record),
|
||||||
actionable: Boolean(record?.mmsi),
|
actionable: Boolean(record?.mmsi),
|
||||||
@@ -86,7 +89,7 @@ export function createCards({
|
|||||||
[
|
[
|
||||||
vesselTypeShort(record) || t('VESSEL'),
|
vesselTypeShort(record) || t('VESSEL'),
|
||||||
formatSpeed(record.speed),
|
formatSpeed(record.speed),
|
||||||
Number.isFinite(direction) ? `${Math.round(direction)}°` : '--°',
|
Number.isFinite(direction) ? `${Math.round(direction) % 360}°` : '--°',
|
||||||
].join(' · '),
|
].join(' · '),
|
||||||
];
|
];
|
||||||
const destination = String(record.destination || '').trim();
|
const destination = String(record.destination || '').trim();
|
||||||
@@ -176,7 +179,7 @@ export function createCards({
|
|||||||
|
|
||||||
function formatHeading(heading) {
|
function formatHeading(heading) {
|
||||||
return t('{heading}DEG', {
|
return t('{heading}DEG', {
|
||||||
heading: Number.isFinite(heading) ? Math.round(heading) : '--',
|
heading: Number.isFinite(heading) ? Math.round(heading) % 360 : '--',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user