Save ais / adsb images to disk
This commit is contained in:
@@ -167,7 +167,7 @@ export class AirTrafficLayer {
|
|||||||
|
|
||||||
const makeProps = (p: any) => ({
|
const makeProps = (p: any) => ({
|
||||||
plane: p,
|
plane: p,
|
||||||
position: mobile ? { x: window.innerWidth / 2 - 180, y: window.innerHeight - 540 - 16 } : { x: window.innerWidth - 360 - 16, y: 16 },
|
position: mobile ? { x: window.innerWidth / 2 - 180, y: window.innerHeight - 540 - 16 } : { x: 16, y: 16 },
|
||||||
onClose: () => this._closePopup(icao),
|
onClose: () => this._closePopup(icao),
|
||||||
onBringToFront: () => bringToFront(container),
|
onBringToFront: () => bringToFront(container),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ export class AISLayer {
|
|||||||
|
|
||||||
const makeProps = (b: any) => ({
|
const makeProps = (b: any) => ({
|
||||||
boat: b,
|
boat: b,
|
||||||
position: mobile ? { x: window.innerWidth / 2 - 180, y: window.innerHeight - 540 - 16 } : { x: window.innerWidth - 360 - 16, y: 16 },
|
position: mobile ? { x: window.innerWidth / 2 - 180, y: window.innerHeight - 540 - 16 } : { x: 16, y: 16 },
|
||||||
onClose: () => this._closePopup(mmsi),
|
onClose: () => this._closePopup(mmsi),
|
||||||
onBringToFront: () => bringToFront(container),
|
onBringToFront: () => bringToFront(container),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ export class SatsLayer {
|
|||||||
history: h_,
|
history: h_,
|
||||||
range: r,
|
range: r,
|
||||||
eta: e,
|
eta: e,
|
||||||
position: mobile ? { x: window.innerWidth / 2 - 180, y: window.innerHeight - 540 - 16 } : { x: window.innerWidth - 360 - 16, y: 16 },
|
position: mobile ? { x: window.innerWidth / 2 - 180, y: window.innerHeight - 540 - 16 } : { x: 16, y: 16 },
|
||||||
onClose: () => this._closePopup(sat.satellite),
|
onClose: () => this._closePopup(sat.satellite),
|
||||||
onBringToFront: () => bringToFront(container),
|
onBringToFront: () => bringToFront(container),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import {cfg} from './config.mjs';
|
import {cfg} from './config.mjs';
|
||||||
|
import * as fs from 'node:fs';
|
||||||
|
import { resolve, dirname } from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
const CHANNEL_MAP = {0: 'A, B', 1: 'A', 2: 'B'};
|
const CHANNEL_MAP = {0: 'A, B', 1: 'A', 2: 'B'};
|
||||||
const SENDER_TYPE = {
|
const SENDER_TYPE = {
|
||||||
@@ -8,6 +11,10 @@ const SENDER_TYPE = {
|
|||||||
12: 'AtoN', 13: 'SART/EPIRB'
|
12: 'AtoN', 13: 'SART/EPIRB'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const DIR = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const DATA = resolve(DIR, '../data');
|
||||||
|
const IMAGES_DIR = resolve(DATA, 'ships');
|
||||||
|
|
||||||
let aisCache = null;
|
let aisCache = null;
|
||||||
let aisCacheTs = 0;
|
let aisCacheTs = 0;
|
||||||
const AIS_TTL = 1000;
|
const AIS_TTL = 1000;
|
||||||
@@ -89,18 +96,47 @@ export async function getAIS() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getAISImage(mmsi) {
|
export async function getAISImage(mmsi) {
|
||||||
const res = await fetch(`https://www.vesselfinder.com/vessels/details/${mmsi}`, {
|
if(!mmsi) return null;
|
||||||
headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36' }
|
mmsi = String(mmsi);
|
||||||
})
|
|
||||||
const html = await res.text()
|
const filePath = resolve(IMAGES_DIR, `${mmsi}.jpg`);
|
||||||
const match = html.match(/<img.+main-photo.+>/)
|
if (fs.existsSync(filePath)) return fs.readFileSync(filePath);
|
||||||
|
|
||||||
|
function randomUA() {
|
||||||
|
const v = 36 + Math.floor(Math.random() * 40);
|
||||||
|
const builds = [
|
||||||
|
`Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.${v} (KHTML, like Gecko) Chrome/1${10 + ~~(Math.random() * 16)}.0.0.0 Safari/537.${v}`,
|
||||||
|
`Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.${v} (KHTML, like Gecko) Chrome/1${10 + ~~(Math.random() * 16)}.0.0.0 Safari/537.${v}`,
|
||||||
|
`Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.${v} (KHTML, like Gecko) Chrome/1${10 + ~~(Math.random() * 16)}.0.0.0 Safari/537.${v}`,
|
||||||
|
];
|
||||||
|
return builds[~~(Math.random() * builds.length)];
|
||||||
|
}
|
||||||
|
|
||||||
|
const html = await fetch(`https://www.vesselfinder.com/vessels/details/${mmsi}`, {
|
||||||
|
headers: {'Accept': '*/*', 'User-Agent': randomUA(), 'Referer': 'https://www.vesselfinder.com/'}
|
||||||
|
}).then(resp => {
|
||||||
|
if(resp.ok) return resp.text();
|
||||||
|
throw new Error('Failed to fetch vessel page');
|
||||||
|
});
|
||||||
|
|
||||||
|
const match = html.match(/<img.+main-photo.+>/);
|
||||||
if(!match) return null;
|
if(!match) return null;
|
||||||
const srcMatch = match[0].match(/src="(.+?)"/)
|
|
||||||
|
const srcMatch = match[0].match(/src="(.+?)"/);
|
||||||
if(!srcMatch) return null;
|
if(!srcMatch) return null;
|
||||||
|
|
||||||
let imgUrl = srcMatch[1]
|
let imgUrl = srcMatch[1];
|
||||||
if (imgUrl.startsWith('/')) imgUrl = `https://www.vesselfinder.com${imgUrl}`
|
if(imgUrl.startsWith('//')) imgUrl = `https:${imgUrl}`;
|
||||||
return fetch(imgUrl, {
|
if(imgUrl.startsWith('/')) imgUrl = `https://www.vesselfinder.com${imgUrl}`;
|
||||||
headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36' }
|
|
||||||
}).then(resp => resp.blob())
|
const resp = await fetch(imgUrl, {
|
||||||
|
headers: {'Accept': '*/*', 'User-Agent': randomUA(), 'Referer': 'https://www.vesselfinder.com/'}
|
||||||
|
});
|
||||||
|
|
||||||
|
if(!resp.ok) throw new Error('Failed to fetch image');
|
||||||
|
|
||||||
|
const buffer = Buffer.from(await resp.arrayBuffer());
|
||||||
|
fs.mkdirSync(IMAGES_DIR, { recursive: true });
|
||||||
|
fs.writeFileSync(filePath, buffer);
|
||||||
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -163,10 +163,8 @@ app.get('/api/adsb/:icao', asyncHandler(async (req, res) => res.json(await getAD
|
|||||||
|
|
||||||
app.get('/api/ais', asyncHandler(async (req, res) => res.json(await getAIS())));
|
app.get('/api/ais', asyncHandler(async (req, res) => res.json(await getAIS())));
|
||||||
app.get('/api/ais/:mmsi/image', asyncHandler(async (req, res) => {
|
app.get('/api/ais/:mmsi/image', asyncHandler(async (req, res) => {
|
||||||
const blob = await getAISImage(req.params.mmsi);
|
const buffer = await getAISImage(req.params.mmsi);
|
||||||
if (!blob) return res.status(404).send('No image found');
|
res.contentType('image/jpeg').send(buffer)
|
||||||
const buffer = Buffer.from(await blob.arrayBuffer());
|
|
||||||
res.contentType(blob.type).send(buffer);
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
app.get('/api/sats', (_req, res) => res.json(getTinyGSData()));
|
app.get('/api/sats', (_req, res) => res.json(getTinyGSData()));
|
||||||
|
|||||||
Reference in New Issue
Block a user