Raccorder les bords de tuiles voisines et encadrer les rendus en cours
Le MNT de chaque dalle s'étend d'une bande de 100 m remplie avec les points sol des 8 tuiles voisines (option « Raccord des bords ») : les rendus à grand noyau (openness, SVF, LRM) deviennent continus d'une dalle à l'autre, les images restent recadrées sur le kilomètre exact. Pendant une génération, la carte encadre les dalles du run : orange pulsant en cours de rendu, rouge en échec — les coins WGS84 sont portés par /api/status pour toutes les dalles non terminées. La file de génération trie les dalles du nord au sud et passe à 10 workers GPU.
This commit is contained in:
@ -1352,8 +1352,8 @@ _HTML_TEMPLATE = """<!DOCTYPE html>
|
||||
<option value="sol,unclassified,eau,virtuel">Sol+non-classé+eau+virtuel (2+1+9+66)</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="gen-opt" title="Ramène le MNT au retour le plus bas de chaque cellule (sol nu)">
|
||||
<input type="checkbox" id="genBareEarth"> Sol nu
|
||||
<label class="gen-opt" title="Complète les bords du MNT avec les points sol des 8 tuiles voisines (bande de 100 m) : les rendus à grand noyau (openness, SVF, LRM) deviennent continus d'une tuile à l'autre. Les images restent recadrées sur la dalle 1 km exacte. Régénère le MNT des tuiles concernées (~1 min de lecture des voisines par tuile en plus).">
|
||||
<input type="checkbox" id="genEdgeBuffer"> Raccord des bords (tuiles voisines)
|
||||
</label>
|
||||
<div class="gen-run">
|
||||
<button id="genGo" class="primary">Lancer</button>
|
||||
@ -1903,6 +1903,12 @@ body.exporting, body.exporting #viewport { cursor: pointer; }
|
||||
}
|
||||
body.genSel, body.genSel #viewport { cursor: pointer; }
|
||||
|
||||
/* Cadres des dalles du run de génération (pane genDraw) : seule la dalle
|
||||
active pulse ; attente (bleu pointillé) et échec (rouge) restent statiques,
|
||||
posés en JS par updateRenderFrames(). */
|
||||
.render-frame.render-running { animation: frame-pulse 1.4s ease-in-out infinite; }
|
||||
@keyframes frame-pulse { 0%, 100% { stroke-opacity: 1; } 50% { stroke-opacity: 0.3; } }
|
||||
|
||||
/* Barres de défilement fines et discrètes */
|
||||
.tab-page::-webkit-scrollbar,
|
||||
.gen-task-log::-webkit-scrollbar { width: 8px; }
|
||||
@ -3019,7 +3025,7 @@ const genClassInfo = document.getElementById('genClassInfo');
|
||||
const genClass = document.getElementById('genClass');
|
||||
const genIgnClasses = document.getElementById('genIgnClasses');
|
||||
const genViz = document.getElementById('genViz');
|
||||
const genBareEarth = document.getElementById('genBareEarth');
|
||||
const genEdgeBuffer = document.getElementById('genEdgeBuffer');
|
||||
const genQueue = document.getElementById('genQueue');
|
||||
const genBadge = document.getElementById('genBadge');
|
||||
const genStop = document.getElementById('genStop');
|
||||
@ -3034,6 +3040,7 @@ let genSelMode = false;
|
||||
const genSel = new Set(); // clés "col,row" des dalles retenues (clics + zones cumulés)
|
||||
const genSelCorners = new Map(); // clé "col,row" → coins GPS (dalles sans données)
|
||||
const genSelPolys = new Map(); // clé "col,row" → polygone Leaflet de surlignage
|
||||
const renderFrames = new Map(); // clé "col,row" → cadre coloré de la dalle en cours de rendu
|
||||
const GEN_MAX_CELLS = 400; // garde-fou serveur : ~400 km² max par demande
|
||||
|
||||
// Affiche la barre de confirmation de génération, dans l'onglet Génération
|
||||
@ -3193,6 +3200,46 @@ function genSelCells() {
|
||||
return [...genSel].map(k => k.split(',').map(Number));
|
||||
}
|
||||
|
||||
// Cadres colorés sur la carte autour des dalles du run en cours : orange
|
||||
// pulsant pour la dalle en cours de rendu, bleu pointillé pour celles qui
|
||||
// attendent, rouge pour les échecs. Les coins viennent de /api/status
|
||||
// (attachés aux dalles non terminées, y compris sans données existantes) ;
|
||||
// un sondage sans coins n'affiche rien — la file de l'onglet Génération
|
||||
// reste la référence.
|
||||
const RENDER_FRAME_STYLES = {
|
||||
running: { color: '#ffb454', weight: 3, fillOpacity: 0.08,
|
||||
className: 'render-frame render-running' },
|
||||
pending: { color: '#5aa9e6', weight: 1.5, dashArray: '6 5', fill: false,
|
||||
className: 'render-frame render-pending' },
|
||||
failed: { color: '#e94560', weight: 2, dashArray: '2 4', fillOpacity: 0.08,
|
||||
className: 'render-frame render-failed' }
|
||||
};
|
||||
|
||||
function updateRenderFrames(tiles) {
|
||||
const wanted = new Map();
|
||||
for (const t of (tiles || [])) {
|
||||
if (t.state === 'done' || !t.corners || t.col == null || t.row == null) continue;
|
||||
if (!RENDER_FRAME_STYLES[t.state]) continue;
|
||||
wanted.set(genSelKey(t.col, t.row), t);
|
||||
}
|
||||
for (const [k, frame] of renderFrames) {
|
||||
if (wanted.has(k) && wanted.get(k).state === frame.state) continue;
|
||||
frame.rect.remove();
|
||||
renderFrames.delete(k);
|
||||
}
|
||||
for (const [k, t] of wanted) {
|
||||
if (renderFrames.has(k)) continue;
|
||||
const rect = L.polygon(t.corners, Object.assign(
|
||||
{ pane: 'genDraw', interactive: false }, RENDER_FRAME_STYLES[t.state])).addTo(map);
|
||||
renderFrames.set(k, { rect: rect, state: t.state });
|
||||
}
|
||||
}
|
||||
|
||||
function clearRenderFrames() {
|
||||
renderFrames.forEach(f => f.rect.remove());
|
||||
renderFrames.clear();
|
||||
}
|
||||
|
||||
function updateGenSelBar() {
|
||||
showGenBar(!genSel.size
|
||||
? 'Mode sélection : clic sur la carte = ajouter / retirer une tuile'
|
||||
@ -3318,7 +3365,7 @@ genGo.addEventListener('click', () => {
|
||||
all_missing: pendingCompleteAll,
|
||||
ground_class: genClass ? genClass.value : 'ign',
|
||||
ign_classes: genIgnClasses ? genIgnClasses.value : 'sol',
|
||||
bare_earth: genBareEarth ? genBareEarth.checked : false,
|
||||
edge_buffer: genEdgeBuffer ? genEdgeBuffer.checked : false,
|
||||
reclassify: genReclass ? genReclass.checked : false })
|
||||
}).then(r => r.ok ? r.json() : r.json().then(j => Promise.reject(j.detail || ('HTTP ' + r.status))))
|
||||
.then(d => {
|
||||
@ -3850,6 +3897,7 @@ function pollGenStatus() {
|
||||
if (d.tiles && d.tiles.length) t.tiles = d.tiles;
|
||||
}
|
||||
if (!d.running) {
|
||||
clearRenderFrames();
|
||||
finishRunningTasks(d);
|
||||
if (genTasks.some(t => t.state === 'queued')) {
|
||||
// Des demandes attendent encore leur tour : poursuivre la veille
|
||||
@ -3867,6 +3915,7 @@ function pollGenStatus() {
|
||||
syncThenReload();
|
||||
} else {
|
||||
saveGenTasks(); renderGenQueue();
|
||||
updateRenderFrames(d.tiles);
|
||||
pollLiveTiles();
|
||||
}
|
||||
}).catch(() => {});
|
||||
@ -3965,6 +4014,7 @@ else fetch('/api/status').then(r => r.ok ? r.json() : null).then(s => {
|
||||
state: 'running', returncode: null, log: [], logOpen: false, tiles: s.tiles || [],
|
||||
});
|
||||
saveGenTasks(); renderGenQueue(); pollGenStatus();
|
||||
updateRenderFrames(s.tiles);
|
||||
} else if (s.returncode !== null && s.tiles && s.tiles.length) {
|
||||
genTasks.push({
|
||||
id: Date.now(), label: 'Dernière génération',
|
||||
|
||||
Reference in New Issue
Block a user