* - coast.bin, border.bin, dept.bin : polylignes pour le rendu C (u32 nb ; u32 nb points, f32 lon, lat) ; * - departements.bin : anneaux extérieurs pour les masques de « austen ensemble » ; * - departements.json : code → nom ; * - basemap.json : côtes et frontières simplifiées (Douglas-Peucker) pour la carte de trajectoire. */ declare(strict_types=1); require __DIR__ . '/lib.php'; const CLIP = [-30.0, 20.0, 38.0, 62.0]; /* cadre utile des cartes, plus un degré de marge */ const TRACK_BOX = [-21.0, 11.0, 45.0, 55.8]; /* carte de trajectoire de la page */ /** Lecture d'un .shp de polylignes ou polygones (format ESRI) : liste de lignes [[lon, lat], ...]. */ function read_shp(string $path): array { $b = file_get_contents($path); if ($b === false || strlen($b) < 100 || unpack('N', $b)[1] !== 9994) { throw new RuntimeException("shapefile invalide : $path"); } $lines = []; $p = 100; $n = strlen($b); while ($p + 8 <= $n) { $len = unpack('N', $b, $p + 4)[1] * 2; /* longueur en mots de 16 bits, gros-boutiste */ $c = $p + 8; if ($len < 4 || $c + $len > $n) { throw new RuntimeException("enregistrement tronqué : $path"); } $type = unpack('V', $b, $c)[1]; if ($type === 3 || $type === 5) { /* polyligne ou polygone */ ['np' => $np, 'nq' => $nq] = unpack('Vnp/Vnq', $b, $c + 36); if ($c + 44 + 4 * $np + 16 * $nq > $c + $len) { throw new RuntimeException("géométrie tronquée : $path"); } $parts = array_values(unpack("V$np", $b, $c + 44)); $parts[] = $nq; $pts = array_values(unpack('e' . (2 * $nq), $b, $c + 44 + 4 * $np)); for ($k = 0; $k < $np; $k++) { $line = []; for ($q = $parts[$k]; $q < $parts[$k + 1]; $q++) { $line[] = [$pts[2 * $q], $pts[2 * $q + 1]]; } $lines[] = $line; } } $p = $c + $len; } return $lines; } function clip(array $lines): array { return array_values(array_filter($lines, function (array $l): bool { foreach ($l as [$x, $y]) { if ($x > CLIP[0] - 1 && $x < CLIP[1] + 1 && $y > CLIP[2] - 1 && $y < CLIP[3] + 1) { return count($l) >= 2; } } return false; })); } function write_lines(string $path, array $lines): void { $out = pack('V', count($lines)); foreach ($lines as $l) { $out .= pack('V', count($l)); foreach ($l as [$x, $y]) { $out .= pack('gg', $x, $y); } } atomic_write($path, $out); } /** Douglas-Peucker itératif. */ function simplify(array $xy, float $tol): array { $n = count($xy); if ($n < 3) { return $xy; } $keep = array_fill(0, $n, false); $keep[0] = $keep[$n - 1] = true; $stack = [[0, $n - 1]]; while ($stack) { [$a, $b] = array_pop($stack); if ($b <= $a + 1) { continue; } [$px, $py] = $xy[$a]; $dx = $xy[$b][0] - $px; $dy = $xy[$b][1] - $py; $norm = hypot($dx, $dy); $best = -1.0; $bi = -1; for ($i = $a + 1; $i < $b; $i++) { $d = $norm > 0 ? abs($dx * ($xy[$i][1] - $py) - $dy * ($xy[$i][0] - $px)) / $norm : hypot($xy[$i][0] - $px, $xy[$i][1] - $py); if ($d > $best) { $best = $d; $bi = $i; } } if ($best > $tol) { $keep[$bi] = true; $stack[] = [$a, $bi]; $stack[] = [$bi, $b]; } } return array_values(array_filter($xy, fn($k) => $keep[$k], ARRAY_FILTER_USE_KEY)); } function basemap_layer(array $lines, float $tol, bool $coast): array { $out = []; foreach ($lines as $l) { $inside = 0; foreach ($l as [$x, $y]) { $inside += ($x > TRACK_BOX[0] - 2 && $x < TRACK_BOX[1] + 2 && $y > TRACK_BOX[2] - 2 && $y < TRACK_BOX[3] + 2); } if ($inside < 2) { continue; } $s = simplify($l, $tol); if (count($s) < 2) { continue; } if ($coast) { /* îlots minuscules écartés */ $xs = array_column($s, 0); $ys = array_column($s, 1); if (max(max($xs) - min($xs), max($ys) - min($ys)) < 0.12) { continue; } } $out[] = array_map(fn($p) => [round($p[0], 2), round($p[1], 2)], $s); } return $out; } function main(array $argv): int { if (count($argv) !== 4) { fwrite(STDERR, "usage : geo_prep.php \n"); return 2; } [, $ne, $geojson, $out] = $argv; ensure_dir($out); $coast = clip(read_shp("$ne/physical/ne_10m_coastline.shp")); $border = clip(read_shp("$ne/cultural/ne_10m_admin_0_boundary_lines_land.shp")); write_lines("$out/coast.bin", $coast); write_lines("$out/border.bin", $border); $g = json_in($geojson); $deptLines = []; $names = []; $bin = 'AUPO' . pack('V', count($g['features'])); foreach ($g['features'] as $f) { $code = (string) $f['properties']['code']; $names[$code] = (string) $f['properties']['nom']; $polys = $f['geometry']['type'] === 'Polygon' ? [$f['geometry']['coordinates']] : $f['geometry']['coordinates']; $bin .= chr(strlen($code)) . $code . pack('V', count($polys)); foreach ($polys as $poly) { foreach ($poly as $ring) { $deptLines[] = $ring; } $outer = $poly[0]; /* masques : anneau extérieur seulement */ $bin .= pack('V', count($outer)); foreach ($outer as [$x, $y]) { $bin .= pack('ee', $x, $y); } } } write_lines("$out/dept.bin", clip($deptLines)); atomic_write("$out/departements.bin", $bin); json_out("$out/departements.json", $names); json_out("$out/basemap.json", ['coast' => basemap_layer($coast, 0.025, true), 'border' => basemap_layer($border, 0.03, false)]); logm('fonds de carte :', (string) count($coast), 'côtes,', (string) count($border), 'frontières,', (string) count($names), 'départements'); return 0; } exit(main($argv));