feat: add rivers overview component and sync lake volume data across the dataset
This commit is contained in:
+15
-3
@@ -56,10 +56,21 @@ const lakes = lakesConfig.map(lake => {
|
||||
|
||||
const metrics = calculateLakeMetrics(currentLevel, volume, lake);
|
||||
|
||||
const cleanText = lake.text.replace(/^VD\s+/, '').replace(/^LG\s+/, '');
|
||||
const parts = cleanText.split('-').map(p => p.trim());
|
||||
let name = '';
|
||||
let river = '';
|
||||
if (parts.length > 1) {
|
||||
river = parts[parts.length - 1];
|
||||
name = parts.slice(0, -1).join(' - ');
|
||||
} else {
|
||||
name = parts[0];
|
||||
}
|
||||
|
||||
return {
|
||||
id: lake.id,
|
||||
name: lake.text.replace('VD ', '').split('-')[0].trim(),
|
||||
river: lake.text.includes('-') ? lake.text.split('-')[1].trim() : '',
|
||||
name,
|
||||
river,
|
||||
priority: lake.priority || false,
|
||||
level: currentLevel.toFixed(2),
|
||||
capacity: metrics.capacity,
|
||||
@@ -71,7 +82,8 @@ const lakes = lakesConfig.map(lake => {
|
||||
navigationForbidden: lake.navigationForbidden || false,
|
||||
lat: lake.coords[0],
|
||||
lng: lake.coords[1],
|
||||
sparkline
|
||||
sparkline,
|
||||
type: lake.type || 'lake'
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
function fixExistingData() {
|
||||
const dataDir = path.resolve('public/data');
|
||||
if (!fs.existsSync(dataDir)) {
|
||||
console.error('Data directory does not exist!');
|
||||
return;
|
||||
}
|
||||
|
||||
const files = fs.readdirSync(dataDir).filter(f => f.endsWith('.json'));
|
||||
console.log(`Found ${files.length} data files to clean up...`);
|
||||
|
||||
files.forEach(file => {
|
||||
const filePath = path.join(dataDir, file);
|
||||
try {
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
const data = JSON.parse(content);
|
||||
|
||||
if (!Array.isArray(data)) return;
|
||||
|
||||
let lastKnownInflow: number | null = null;
|
||||
let lastKnownVolume: number | null = null;
|
||||
let fixCountInflow = 0;
|
||||
let fixCountVolume = 0;
|
||||
|
||||
// First pass (oldest to newest): find and propagate values forward
|
||||
data.forEach(record => {
|
||||
// Handle inflow
|
||||
if (record.inflow !== undefined && record.inflow !== null && record.inflow !== 0) {
|
||||
lastKnownInflow = record.inflow;
|
||||
} else if ((record.inflow === undefined || record.inflow === null || record.inflow === 0) && lastKnownInflow !== null) {
|
||||
record.inflow = lastKnownInflow;
|
||||
fixCountInflow++;
|
||||
}
|
||||
|
||||
// Handle volume
|
||||
if (record.volume !== undefined && record.volume !== null && record.volume !== 0) {
|
||||
lastKnownVolume = record.volume;
|
||||
} else if ((record.volume === undefined || record.volume === null || record.volume === 0) && lastKnownVolume !== null) {
|
||||
record.volume = lastKnownVolume;
|
||||
fixCountVolume++;
|
||||
}
|
||||
});
|
||||
|
||||
// Second pass (newest to oldest): if there were zeros at the very beginning of the file
|
||||
// before any non-zero value was found, propagate backwards.
|
||||
let nextKnownInflow: number | null = null;
|
||||
let nextKnownVolume: number | null = null;
|
||||
for (let i = data.length - 1; i >= 0; i--) {
|
||||
const record = data[i];
|
||||
if (record.inflow !== undefined && record.inflow !== null && record.inflow !== 0) {
|
||||
nextKnownInflow = record.inflow;
|
||||
} else if ((record.inflow === undefined || record.inflow === null || record.inflow === 0) && nextKnownInflow !== null) {
|
||||
record.inflow = nextKnownInflow;
|
||||
fixCountInflow++;
|
||||
}
|
||||
|
||||
if (record.volume !== undefined && record.volume !== null && record.volume !== 0) {
|
||||
nextKnownVolume = record.volume;
|
||||
} else if ((record.volume === undefined || record.volume === null || record.volume === 0) && nextKnownVolume !== null) {
|
||||
record.volume = nextKnownVolume;
|
||||
fixCountVolume++;
|
||||
}
|
||||
}
|
||||
|
||||
if (fixCountInflow > 0 || fixCountVolume > 0) {
|
||||
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
|
||||
console.log(`[${file}] Cleaned up ${fixCountInflow} inflows and ${fixCountVolume} volumes.`);
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.error(`Error processing file ${file}:`, e.message);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Cleanup finished.');
|
||||
}
|
||||
|
||||
fixExistingData();
|
||||
+19
-1
@@ -8,6 +8,7 @@ export interface LakeConfig {
|
||||
maxLevel?: number;
|
||||
storageLevel?: number;
|
||||
navigationForbidden?: boolean;
|
||||
type?: 'lake' | 'river';
|
||||
}
|
||||
|
||||
export const lakesConfig: LakeConfig[] = [
|
||||
@@ -49,5 +50,22 @@ export const lakesConfig: LakeConfig[] = [
|
||||
{ id: "SPZH|1", text: "VD Zhejral", coords: [49.2310, 15.3120], maxVolume: 0.2, minLevel: 675.2, maxLevel: 679.7, storageLevel: 678.6, navigationForbidden: true },
|
||||
{ id: "KLDP|3", text: "VD Dolejší Padrťský rybník", coords: [49.6640, 13.7530], maxVolume: 0.5, minLevel: 632.69, maxLevel: 634.29, storageLevel: 632.89, navigationForbidden: true },
|
||||
{ id: "KLHP|3", text: "VD Hořejší Padrťský rybník", coords: [49.6550, 13.7610], maxVolume: 0.7, minLevel: 635.76, maxLevel: 637.56, storageLevel: 636.36, navigationForbidden: true },
|
||||
{ id: "CPDR|3", text: "VD Dráteník", coords: [49.8050, 13.8550], maxVolume: 0.1, minLevel: 413.75, maxLevel: 417.91, storageLevel: 416.68, navigationForbidden: false }
|
||||
{ id: "CPDR|3", text: "VD Dráteník", coords: [49.8050, 13.8550], maxVolume: 0.1, minLevel: 413.75, maxLevel: 417.91, storageLevel: 416.68, navigationForbidden: false },
|
||||
|
||||
// Rivers
|
||||
{ id: "VLCH|2", text: "LG Praha - Malá Chuchle - Vltava", coords: [50.0294, 14.3986], type: 'river' },
|
||||
{ id: "VLCB|1", text: "LG České Budějovice - Vltava", coords: [48.9712, 14.4714], type: 'river' },
|
||||
{ id: "BEBE|3", text: "LG Beroun - Berounka", coords: [49.9642, 14.0792], type: 'river' },
|
||||
{ id: "SANE|2", text: "LG Nespeky - Sázava", coords: [49.8596, 14.5888], type: 'river' },
|
||||
{ id: "OTPI|1", text: "LG Písek - Otava", coords: [49.3083, 14.1436], type: 'river' },
|
||||
{ id: "OTSU|1", text: "LG Sušice - Otava", coords: [49.2319, 13.5186], type: 'river' },
|
||||
{ id: "LUBE|1", text: "LG Bechyně - Lužnice", coords: [49.2931, 14.4758], type: 'river' },
|
||||
{ id: "LUKL|1", text: "LG Klenovice - Lužnice", coords: [49.3402, 14.7175], type: 'river' },
|
||||
{ id: "SAZR|2", text: "LG Zruč nad Sázavou - Sázava", coords: [49.7428, 15.1011], type: 'river' },
|
||||
{ id: "SASV|2", text: "LG Světlá nad Sázavou - Sázava", coords: [49.6677, 15.4048], type: 'river' },
|
||||
{ id: "SAKA|2", text: "LG Kácov - Sázava", coords: [49.7772, 15.0294], type: 'river' },
|
||||
{ id: "BEZB|3", text: "LG Zbečno - Berounka", coords: [50.0436, 13.9189], type: 'river' },
|
||||
{ id: "BEPL|3", text: "LG Plzeň - Bílá Hora - Berounka", coords: [49.7731, 13.3986], type: 'river' },
|
||||
{ id: "VLVB|1", text: "LG Vyšší Brod - Vltava", coords: [48.6167, 14.3167], type: 'river' }
|
||||
];
|
||||
|
||||
|
||||
+36
-6
@@ -38,7 +38,11 @@ export function parseDateString(dateStr: string): string | null {
|
||||
}
|
||||
|
||||
async function scrapeLake(lakeId: string, oid: string, internalId: string) {
|
||||
const URL = `https://www.pvl.cz/portal/nadrze/cz/pc/Mereni.aspx?oid=${oid}&id=${internalId}`;
|
||||
const config = lakesConfig.find(l => l.id === lakeId);
|
||||
const isRiver = config?.type === 'river';
|
||||
const URL = isRiver
|
||||
? `https://www.pvl.cz/portal/sap/cz/pc/Mereni.aspx?oid=${oid}&id=${internalId}`
|
||||
: `https://www.pvl.cz/portal/nadrze/cz/pc/Mereni.aspx?oid=${oid}&id=${internalId}`;
|
||||
const DATA_FILE = path.resolve(`public/data/${internalId}.json`);
|
||||
|
||||
try {
|
||||
@@ -80,7 +84,8 @@ async function scrapeLake(lakeId: string, oid: string, internalId: string) {
|
||||
const records: DataRecord[] = [];
|
||||
let dataTable = null;
|
||||
$('table').each((i, tbl) => {
|
||||
if ($(tbl).text().includes('Datum') && $(tbl).text().includes('Odtok')) {
|
||||
const id = ($(tbl).attr('id') || '').toLowerCase();
|
||||
if (id.includes('datamereni24hgv') || id.includes('datamerenigv')) {
|
||||
dataTable = $(tbl);
|
||||
}
|
||||
});
|
||||
@@ -102,9 +107,7 @@ async function scrapeLake(lakeId: string, oid: string, internalId: string) {
|
||||
records.push({
|
||||
timestamp: parsedDateStr,
|
||||
level: parseFloat(levelStr) || 0,
|
||||
flow: parseFloat(flowStr) || 0,
|
||||
inflow: 0,
|
||||
volume: 0
|
||||
flow: parseFloat(flowStr) || 0
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -143,7 +146,19 @@ async function scrapeLake(lakeId: string, oid: string, internalId: string) {
|
||||
|
||||
const dataMap = new Map<string, DataRecord>();
|
||||
existingData.forEach(item => dataMap.set(item.timestamp, item));
|
||||
records.forEach(item => dataMap.set(item.timestamp, item));
|
||||
records.forEach(item => {
|
||||
const existing = dataMap.get(item.timestamp);
|
||||
if (existing) {
|
||||
dataMap.set(item.timestamp, {
|
||||
...existing,
|
||||
...item,
|
||||
inflow: item.inflow !== undefined ? item.inflow : existing.inflow,
|
||||
volume: item.volume !== undefined ? item.volume : existing.volume
|
||||
});
|
||||
} else {
|
||||
dataMap.set(item.timestamp, item);
|
||||
}
|
||||
});
|
||||
|
||||
const mergedData = Array.from(dataMap.values()).sort((a, b) => {
|
||||
return new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime();
|
||||
@@ -152,6 +167,9 @@ async function scrapeLake(lakeId: string, oid: string, internalId: string) {
|
||||
// Propagate previous values if missing (user requested)
|
||||
let lastKnownTemp: number | null = null;
|
||||
let lastKnownPrecip: number | null = null;
|
||||
let lastKnownInflow: number | undefined = undefined;
|
||||
let lastKnownVolume: number | undefined = undefined;
|
||||
|
||||
mergedData.forEach(item => {
|
||||
if (item.temperature !== undefined && item.temperature !== null) {
|
||||
lastKnownTemp = item.temperature;
|
||||
@@ -164,6 +182,18 @@ async function scrapeLake(lakeId: string, oid: string, internalId: string) {
|
||||
} else if (lastKnownPrecip !== null) {
|
||||
item.precipitation = lastKnownPrecip;
|
||||
}
|
||||
|
||||
if (item.inflow !== undefined && item.inflow !== null) {
|
||||
lastKnownInflow = item.inflow;
|
||||
} else if (lastKnownInflow !== undefined) {
|
||||
item.inflow = lastKnownInflow;
|
||||
}
|
||||
|
||||
if (item.volume !== undefined && item.volume !== null) {
|
||||
lastKnownVolume = item.volume;
|
||||
} else if (lastKnownVolume !== undefined) {
|
||||
item.volume = lastKnownVolume;
|
||||
}
|
||||
});
|
||||
|
||||
fs.mkdirSync(path.dirname(DATA_FILE), { recursive: true });
|
||||
|
||||
Reference in New Issue
Block a user