'20261024T0300' || is_file("$dst/$f")) { continue; } $data = read_untrusted(SAT_SRC . "/$f", 30_000_000); if ($data !== null && str_starts_with($data, 'RIFF') && substr($data, 8, 4) === 'WEBP') { atomic_write("$dst/$f", $data); $n++; } } return $n; } /** Position du centre à l'instant $t, interpolée entre deux points distants de 6 h au plus. */ function center_at(array $analysis, int $t): ?array { for ($k = 0; $k + 1 < count($analysis); $k++) { $a = $analysis[$k]; $b = $analysis[$k + 1]; $ta = utc($a['t']); $tb = utc($b['t']); if ($t >= $ta && $t <= $tb && $tb - $ta <= 6 * 3600) { $f = $tb > $ta ? ($t - $ta) / ($tb - $ta) : 0; return [round($a['lat'] + $f * ($b['lat'] - $a['lat']), 2), round($a['lon'] + $f * ($b['lon'] - $a['lon']), 2), round($a['p'] + $f * ($b['p'] - $a['p']))]; } } return null; } function animate(string $src, string $pub): int { $dir = "$pub/sat/ir"; ensure_dir($dir); $indexPath = "$pub/sat/index.json"; $index = is_file($indexPath) ? json_in($indexPath) : ['frames' => []]; $known = array_column($index['frames'], null, 'k'); $data = is_file("$pub/data.json") ? json_in("$pub/data.json") : []; $analysis = $data['analysis'] ?? []; $geo = data_dir() . '/geo'; $spec = ''; $frames = []; foreach (glob("$src/sat_ir108_*_m.webp") ?: [] as $f) { if (!preg_match('/_(\d{8})T(\d{2})(\d{2})_m\.webp$/', $f, $m)) { continue; } $t = gmmktime((int) $m[2], (int) $m[3], 0, (int) substr($m[1], 4, 2), (int) substr($m[1], 6, 2), (int) substr($m[1], 0, 4)); $k = gmdate('YmdHi', $t); $c = center_at($analysis, $t); $frames[] = ['k' => $k, 'd' => $c]; if (isset($known[$k]) && $known[$k]['d'] === $c && is_file("$dir/$k.webp")) { continue; } $d = (new DateTimeImmutable('@' . $t))->setTimezone(new DateTimeZone('Europe/Paris')); $when = JOURS[(int) $d->format('w')] . ' ' . $d->format('j') . ' à ' . $d->format('G') . ' h ' . $d->format('i'); $b = ['out' => "$dir/$k.webp", 'quality' => 80, 'image' => $f, 'box' => implode(' ', [SAT_BOX[0], SAT_BOX[1], SAT_BOX[2], SAT_BOX[3]]), 'geo' => "$geo 1 0.8", 'geocolor' => '#f2c94c', 'title' => 'Austen vue par Meteosat (infrarouge 10,8 µm)', 'sub' => mb_strtoupper(mb_substr($when, 0, 1)) . mb_substr($when, 1) . ' (heure de Paris)', 'right' => 'MSG, EUMETSAT', 'sign' => 'Meteociel.fr']; if ($c) { $b['center'] = sprintf('%.2f %.2f %d', $c[1], $c[0], $c[2]); } foreach ($b as $kk => $v) { $spec .= $kk . ' ' . str_replace(["\n", "\r"], ' ', (string) $v) . "\n"; } $spec .= "end\n"; } if ($spec !== '') { $tmp = data_dir() . '/render/sat_spec.txt'; atomic_write($tmp, $spec); [$code, , $err] = run([BIN, 'render', $tmp], 600); if ($code !== 0) { logm('ÉCHEC images satellite', trim($err)); return 0; } } usort($frames, fn($a, $b) => strcmp($a['k'], $b['k'])); $index['updated'] = gmdate('Y-m-d\TH:i:s\Z'); $index['box'] = SAT_BOX; $index['frames'] = $frames; json_out($indexPath, $index); return substr_count($spec, "end\n"); } /** Vue Europe : images de Meteociel, une toutes les 5 minutes, réenregistrées en JPEG (environ 100 Ko). */ function europe(string $pub): int { $dir = "$pub/sat/eu"; ensure_dir($dir); $indexPath = "$pub/sat/index.json"; $n = 0; $end = min(time() - 600, utc('2026-10-24T03:00Z')); for ($t = utc('2026-10-22T12:00Z'); $t <= $end && $n < 80; $t += 300) { $k = gmdate('YmdHi', $t); if (is_file("$dir/$k.jpg")) { continue; } $url = sprintf('https://modeles20.meteociel.fr/satellite/archives/msgrss_%s/satirmsgeu-%s-%s.png', gmdate('Ymd', $t), gmdate('H', $t), gmdate('i', $t)); [$code, $png] = http_get($url, [], null, 3 << 20); if ($code !== 200 || !str_starts_with($png, "\x89PNG\r\n\x1a\n")) { continue; /* image absente chez Meteociel : on réessaiera au passage suivant */ } $im = @imagecreatefromstring($png); if ($im === false || imagesx($im) > 2000 || imagesy($im) > 2000) { continue; } $tmp = "$dir/.$k.jpg.new"; imagejpeg($im, $tmp, 75); chmod($tmp, 0644); rename($tmp, "$dir/$k.jpg"); $n++; usleep(300_000); /* téléchargements espacés */ } $list = array_map(fn($f) => basename($f, '.jpg'), glob("$dir/*.jpg") ?: []); sort($list); $index = is_file($indexPath) ? json_in($indexPath) : ['frames' => []]; $index['eu'] = $list; json_out($indexPath, $index); return $n; } function main(): int { if (time() > utc('2026-10-24T12:00Z')) { return 0; } $dst = data_dir() . '/sat_archive'; ensure_dir($dst); $n = archive($dst); $r = animate($dst, pub_dir()); $e = europe(pub_dir()); if ($n || $r || $e) { logm('satellite :', (string) $n, 'images copiées,', (string) $r, 'images composées,', (string) $e, 'images Europe'); } return 0; } exit(main());