Files
mek-tech-showcase/server/lib/sources/smart-city-infra.js
T

119 lines
7.8 KiB
JavaScript

/** Smart City infrastructure — Amsterdam / Randstad focus, realistic coordinates */
const PUBLIC_HLS = [
'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
'https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel.ism/.m3u8',
'https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8',
'https://cph-p2p-msl.akamaized.net/hls/live/2000341/test/master.m3u8'
];
const PUBLIC_MP4 = [
'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',
'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4',
'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4'
];
/** Live YouTube 24/7 NL city webcams — embed-friendly streams */
const YOUTUBE_BY_CITY = {
Amsterdam: 'Bvo4l7LP-nc',
'Amsterdam CS': 'Bvo4l7LP-nc',
Rotterdam: 'MbetYro0DO8',
'Den Haag': '0SIEe-jH-Gw',
Utrecht: 'va3DX5Mg7YM',
Eindhoven: '8lsA80yEi2A'
};
const CAMERAS = [
{ id: 'cam-dam', name: 'Dam Square — Noord', city: 'Amsterdam', lat: 52.3731, lng: 4.8932, type: 'traffic', zone: 'Centrum', streamIdx: 0, youtubeId: YOUTUBE_BY_CITY.Amsterdam, install: '2019', resolution: '4K' },
{ id: 'cam-cs', name: 'Centraal Station — Hoofdingang', city: 'Amsterdam', lat: 52.3791, lng: 4.9003, type: 'security', zone: 'Centrum', streamIdx: 1, youtubeId: YOUTUBE_BY_CITY['Amsterdam CS'], install: '2021', resolution: '1080p' },
{ id: 'cam-ij', name: 'IJ-tunnel — Westelijke Oprit', city: 'Amsterdam', lat: 52.3847, lng: 4.9021, type: 'traffic', zone: 'Noord', streamIdx: 2, youtubeId: YOUTUBE_BY_CITY.Amsterdam, install: '2018', resolution: '1080p' },
{ id: 'cam-a10', name: 'A10 Ring — Coentunnel Zuid', city: 'Amsterdam', lat: 52.4012, lng: 4.8688, type: 'traffic', zone: 'Westpoort', streamIdx: 0, youtubeId: YOUTUBE_BY_CITY.Amsterdam, install: '2020', resolution: '4K' },
{ id: 'cam-museum', name: 'Museumplein — Fietsenstalling', city: 'Amsterdam', lat: 52.3579, lng: 4.8811, type: 'security', zone: 'Zuid', streamIdx: 1, youtubeId: YOUTUBE_BY_CITY.Amsterdam, install: '2022', resolution: '1080p' },
{ id: 'cam-zuidas', name: 'Zuidas — Mahlerplein', city: 'Amsterdam', lat: 52.3383, lng: 4.8724, type: 'traffic', zone: 'Zuidas', streamIdx: 2, youtubeId: YOUTUBE_BY_CITY.Amsterdam, install: '2023', resolution: '4K' },
{ id: 'cam-rot-cs', name: 'Rotterdam CS — Proveniers', city: 'Rotterdam', lat: 51.9244, lng: 4.4692, type: 'security', zone: 'Centrum', streamIdx: 0, youtubeId: YOUTUBE_BY_CITY.Rotterdam, install: '2021', resolution: '1080p' },
{ id: 'cam-rot-erasmus', name: 'Erasmusbrug — Noord', city: 'Rotterdam', lat: 51.9087, lng: 4.4863, type: 'traffic', zone: 'Kop van Zuid', streamIdx: 1, youtubeId: YOUTUBE_BY_CITY.Rotterdam, install: '2019', resolution: '4K' },
{ id: 'cam-dh-bin', name: 'Den Haag — Binnenhof', city: 'Den Haag', lat: 52.0796, lng: 4.3131, type: 'security', zone: 'Centrum', streamIdx: 2, youtubeId: YOUTUBE_BY_CITY['Den Haag'], install: '2020', resolution: '1080p' },
{ id: 'cam-ut-cs', name: 'Utrecht CS — Jaarbeurszijde', city: 'Utrecht', lat: 52.0893, lng: 5.1106, type: 'traffic', zone: 'Centrum', streamIdx: 0, youtubeId: YOUTUBE_BY_CITY.Utrecht, install: '2022', resolution: '1080p' },
{ id: 'cam-eind-hs', name: 'Eindhoven — Strijp-S', city: 'Eindhoven', lat: 51.4514, lng: 5.4842, type: 'security', zone: 'Strijp', streamIdx: 1, youtubeId: YOUTUBE_BY_CITY.Eindhoven, install: '2023', resolution: '4K' },
{ id: 'cam-har-port', name: 'Den Haag — Haven', city: 'Den Haag', lat: 52.0954, lng: 4.2658, type: 'traffic', zone: 'Haven', streamIdx: 2, youtubeId: YOUTUBE_BY_CITY['Den Haag'], install: '2018', resolution: '1080p' }
];
const LAMPPOSTS = [
{ id: 'lp-001', lat: 52.3728, lng: 4.8945, street: 'Damrak', brightness: 78, energyWh: 124, motion: true, camLinked: 'cam-dam' },
{ id: 'lp-002', lat: 52.3742, lng: 4.8918, street: 'Rokin', brightness: 65, energyWh: 98, motion: true, camLinked: 'cam-dam' },
{ id: 'lp-003', lat: 52.3785, lng: 4.9015, street: 'Oosterdok', brightness: 90, energyWh: 156, motion: false, camLinked: 'cam-cs' },
{ id: 'lp-004', lat: 52.3812, lng: 4.8998, street: 'Oosterdokskade', brightness: 55, energyWh: 87, motion: true, camLinked: 'cam-cs' },
{ id: 'lp-005', lat: 52.3835, lng: 4.9035, street: 'Piet Heinkade', brightness: 72, energyWh: 112, motion: false, camLinked: 'cam-ij' },
{ id: 'lp-006', lat: 52.3565, lng: 4.8825, street: 'Van Baerlestraat', brightness: 48, energyWh: 76, motion: true, camLinked: 'cam-museum' },
{ id: 'lp-007', lat: 52.3395, lng: 4.8735, street: 'Gustav Mahlerlaan', brightness: 82, energyWh: 134, motion: true, camLinked: 'cam-zuidas' },
{ id: 'lp-008', lat: 52.3378, lng: 4.8712, street: 'Beethovenstraat', brightness: 60, energyWh: 95, motion: false, camLinked: 'cam-zuidas' },
{ id: 'lp-009', lat: 52.4005, lng: 4.8675, street: 'Sloterdijkweg', brightness: 95, energyWh: 168, motion: true, camLinked: 'cam-a10' },
{ id: 'lp-010', lat: 52.9235, lng: 4.4712, street: 'Proveniersstraat', brightness: 70, energyWh: 108, motion: true, camLinked: 'cam-rot-cs' },
{ id: 'lp-011', lat: 52.9075, lng: 4.4878, street: 'Wilhelminakade', brightness: 88, energyWh: 142, motion: false, camLinked: 'cam-rot-erasmus' },
{ id: 'lp-012', lat: 52.0788, lng: 4.3145, street: 'Hofweg', brightness: 52, energyWh: 82, motion: true, camLinked: 'cam-dh-bin' },
{ id: 'lp-013', lat: 52.0885, lng: 5.1095, street: 'Moreelsepark', brightness: 68, energyWh: 105, motion: false, camLinked: 'cam-ut-cs' },
{ id: 'lp-014', lat: 52.4505, lng: 5.4835, street: 'Torenallee', brightness: 75, energyWh: 118, motion: true, camLinked: 'cam-eind-hs' },
{ id: 'lp-015', lat: 52.3735, lng: 4.8965, street: 'Paleisstraat', brightness: 42, energyWh: 68, motion: true, camLinked: 'cam-dam' },
{ id: 'lp-016', lat: 52.3708, lng: 4.8922, street: 'Dam', brightness: 58, energyWh: 91, motion: true, camLinked: 'cam-dam' }
];
let tick = 0;
function jitter(v, pct = 0.06) {
return Math.max(1, +(v * (1 + (Math.random() - 0.5) * pct * 2)).toFixed(1));
}
function getStreamUrl(idx) {
return PUBLIC_HLS[idx % PUBLIC_HLS.length];
}
function getMp4Url(idx) {
return PUBLIC_MP4[idx % PUBLIC_MP4.length];
}
function getCameraById(id) {
const cam = CAMERAS.find(c => c.id === id);
if (!cam) return null;
return {
...cam,
streamUrl: getStreamUrl(cam.streamIdx),
mp4Url: getMp4Url(cam.streamIdx),
frameUrl: `/api/smart-city/camera/${cam.id}/frame`,
online: Math.random() > 0.02,
fps: 24 + Math.floor(Math.random() * 6),
bitrate: 4 + Math.floor(Math.random() * 8),
viewers: Math.floor(Math.random() * 12) + 1,
lastMotion: new Date(Date.now() - Math.random() * 120000).toISOString()
};
}
function evolveInfrastructure() {
tick++;
const cameras = CAMERAS.map(c => ({
...c,
streamUrl: getStreamUrl(c.streamIdx),
mp4Url: getMp4Url(c.streamIdx),
frameUrl: `/api/smart-city/camera/${c.id}/frame`,
online: Math.random() > 0.015,
fps: 24 + Math.floor(Math.random() * 8),
bitrateMbps: +(3.5 + Math.random() * 6).toFixed(1),
latencyMs: Math.round(80 + Math.random() * 120),
recording: true,
aiTags: ['vehicle', 'pedestrian', 'bicycle'].filter(() => Math.random() > 0.4).slice(0, 2)
}));
const lampposts = LAMPPOSTS.map(lp => ({
...lp,
brightness: Math.round(Math.min(100, Math.max(20, lp.brightness + (Math.random() - 0.5) * 8))),
energyWh: Math.round(jitter(lp.energyWh, 0.04)),
motion: Math.random() > 0.55,
lux: Math.round(20 + Math.random() * 80),
tempC: +(15 + Math.random() * 8).toFixed(1),
co2saved: +(0.02 + Math.random() * 0.08).toFixed(3)
}));
return { cameras, lampposts };
}
module.exports = { CAMERAS, LAMPPOSTS, getCameraById, evolveInfrastructure, getStreamUrl, PUBLIC_HLS, PUBLIC_MP4 };