Files
training/website/scripts/gen-yunnan-map.mjs
T
Pine b2ddcb1641 feat(website): 云南载体地图(点阵地图+载体列表+园区详情)
- 顶级页 #/map:16 州市边界点阵地图(构建期预计算 yunnan-map.json,Canvas 按容器缩放绘制+ResizeObserver 重绘),载体点位悬浮卡、市域着色与名称标注,全出血占满屏宽
- #/parks 载体列表、#/park-detail 园区详情(位置高德外链/介绍/政策/入驻流程/在园企业)
- 导航加「载体」入口;依赖 @suxiong/react-dotted-map 与 dompurify;gen:yunnan-map 脚本可再生成点阵

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-31 20:39:01 +08:00

62 lines
2.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 离线预计算云南点阵地图(构建期一次性执行,替代运行时 1~2s 的地理计算)。
*
* node scripts/gen-yunnan-map.mjs
*
* 读取 src/data/yunnan-cities.json16 州市边界 GeoJSON),
* 生成 src/data/yunnan-map.json(紧凑点阵 + 坐标系参数 + 州市标注锚点)。
* 运行时 ParkMap.jsx 直接 import 该 JSON 重建 MapConfig,零地理计算。
*/
import { readFileSync, writeFileSync } from 'node:fs';
import { generateMap } from '@suxiong/react-dotted-map';
const MAP_W = 980;
const MAP_H = 1064;
const SPACING = 3.4;
const REGION = { lat: { min: 21.0, max: 29.4 }, lng: { min: 97.3, max: 106.4 } };
const geo = JSON.parse(readFileSync(new URL('../src/data/yunnan-cities.json', import.meta.url), 'utf8'));
const cities = geo.features.map((f) => f.properties.name);
const geojsonByCountry = Object.fromEntries(geo.features.map((f) => [f.properties.name, f]));
const t0 = Date.now();
const map = generateMap({
width: MAP_W, height: MAP_H, grid: 'diagonal', spacing: SPACING,
region: REGION, countries: cities, geojsonByCountry,
detectCountries: true, targetCountries: cities,
});
const pts = Object.values(map.points);
console.log(`generateMap: ${pts.length} points in ${Date.now() - t0}ms`);
// 州市标注锚点:最大环的面积加权质心
function ringCentroid(ring) {
let a = 0, cx = 0, cy = 0;
for (let i = 0; i < ring.length - 1; i++) {
const [x1, y1] = ring[i], [x2, y2] = ring[i + 1];
const c = x1 * y2 - x2 * y1;
a += c; cx += (x1 + x2) * c; cy += (y1 + y2) * c;
}
if (Math.abs(a) < 1e-9) return ring[0];
return [cx / (3 * a), cy / (3 * a)];
}
const labels = geo.features.map((f) => {
const g = f.geometry;
const polys = g.type === 'MultiPolygon' ? g.coordinates : [g.coordinates];
let best = null, n = -1;
polys.forEach((p) => { if (p[0].length > n) { n = p[0].length; best = p[0]; } });
const [lng, lat] = ringCentroid(best);
return { name: f.properties.name, lat: +lat.toFixed(5), lng: +lng.toFixed(5) };
});
// 紧凑编码:pts = [x, y, cityIndex],坐标体系参数原样保留(latLngToScreenCoords 依赖)
const out = {
w: map.width, h: map.height, grid: map.grid, ystep: map.ystep,
xmin: map.X_MIN, ymin: map.Y_MIN, xmax: map.X_MAX, ymax: map.Y_MAX,
xr: map.X_RANGE, yr: map.Y_RANGE, region: map.region,
cities,
labels,
pts: pts.map((p) => [p.x, p.y, cities.indexOf(p.countryCode || '')]),
};
writeFileSync(new URL('../src/data/yunnan-map.json', import.meta.url), JSON.stringify(out));
console.log(`written src/data/yunnan-map.json (${(JSON.stringify(out).length / 1024).toFixed(0)}KB)`);