LUTAGU 地圖節點顯示規則。定義五層級節點顯示邏輯、Hub 聚合機制、 Zoom-based 可見性控制、鐵道公司顏色標準。確保 UI 開發符合規範。
規範前端地圖節點的顯示邏輯,確保:
東京站、上野、池袋、新宿、澀谷、品川、銀座、秋葉原
成田機場 (NRT)、羽田機場 (HND)
display_tier = 1, min_zoom_level = 1Zoom >= 12 顯示名稱大手町、淺草、押上、新橋、東日本橋、濱松町、御徒町、
御茶之水、飯田橋、中目黑、日暮里、泉岳寺、大井町、蒲田等
display_tier = 2, min_zoom_level = 12Zoom >= 14 顯示名稱display_tier = 3, min_zoom_level = 14Zoom >= 15 顯示名稱daily_passengers 欄位 (降序)display_tier = 4, min_zoom_level = 15Zoom >= 16 顯示名稱display_tier = 5, min_zoom_level = 16上野JR 上野站、上野駅 (JR/東京メトロ)| 鐵道公司 | 品牌色 (HEX) | 適用範例 |
|---|---|---|
| JR 東日本 | #00AC4E (綠色) | 上野、東京、池袋、新宿、澀谷、品川、秋葉原 |
| 東京 Metro | #149BDF (藍綠色) | 銀座、大手町、日本橋 |
| 都營地下鐵 | #70BE1B (淺綠色) | 新橋、押上 (依循用戶指定代表色) |
| 京急電鐵 | #E31E24 (紅色) | 泉岳寺、品川 (京急優先時) |
| 東武鐵道 | #003DA5 (深藍色) | 淺草、押上 (東武優先時) |
| 京成電鐵 | #003DA5 (深藍色) | 成田機場、日暮里 |
| 東京單軌電車 | #0071BC (藍色) | 羽田機場 |
JR 東日本 > 東京 Metro > 都營地下鐵 > 私鐵 (京急/東武/京成等)
#003DA5)#0071BC)#149BDF)40x40px (最大)32x32px24x24pxborder-radius: 8px)2px solid #FFFFFFbox-shadow: 0 2px 8px rgba(0,0,0,0.15)🚇 或鐵道公司 Logo (如有授權)display_tier <= 2 且 is_hub = truedisplay_tier 層級/**
* 判斷節點是否應該顯示 (Marker 可見性)
*/
function shouldShowNode(node: Node, currentZoom: number): boolean {
return currentZoom >= node.min_zoom_level;
}
/**
* 判斷節點名稱是否應該顯示 (Label 可見性)
*/
function shouldShowLabel(node: Node, currentZoom: number): boolean {
// Tier 1: 永遠顯示名稱
if (node.display_tier === 1) return true;
// Tier 2: Zoom >= 12 顯示
if (node.display_tier === 2) return currentZoom >= 12;
// Tier 3: Zoom >= 14 顯示
if (node.display_tier === 3) return currentZoom >= 14;
// Tier 4-5: Zoom >= 15 顯示
return currentZoom >= 15;
}
/**
* 取得節點主要鐵道公司顏色
*/
function getNodeColor(node: Node): string {
const operatorPriority = [
{ name: 'JR東日本', color: '#00AC4E' },
{ name: '東京メトロ', color: '#149BDF' },
{ name: '東京都交通局', color: '#B6007A' },
{ name: '京急電鉄', color: '#E31E24' },
{ name: '東武鉄道', color: '#003DA5' },
{ name: '京成電鉄', color: '#003DA5' },
];
// 特殊案例處理
if (node.id.includes('Narita')) return '#003DA5'; // 成田機場
if (node.id.includes('Haneda')) return '#0071BC'; // 羽田機場
// 從 node.operators 找第一個匹配的公司
for (const op of operatorPriority) {
if (node.operators?.some(o => o.includes(op.name))) {
return op.color;
}
}
// 預設灰色
return '#6B7280';
}
| Zoom Level | 可見節點層級 | 說明 |
|---|---|---|
| 1-11 | Tier 1 only | 僅超級樞紐 (關東全區) |
| 12-13 | Tier 1-2 | 主要樞紐開始顯示 |
| 14 | Tier 1-3 | 次要樞紐開始顯示 |
| 15 | Tier 1-4 | 常用車站開始顯示 |
| 16+ | Tier 1-5 | 所有車站顯示 |
useViewportBounds + useVisibleMarkers// ✅ 正確: 使用 useMemo 快取過濾結果
const visibleNodes = useMemo(() => {
return allNodes.filter(n => shouldShowNode(n, zoom));
}, [allNodes, zoom]);
// ✅ 正確: 使用 memo 包裝高頻渲染元件
const NodeMarker = memo(({ node, zoom }) => {
// ...
});
map.on('move') 中直接操作 DOMzoomend 事件而非 zoom 事件Zoom >= 1624x24px + ⭐ emoji10x10pxZoom >= 17CREATE TABLE nodes (
id UUID PRIMARY KEY,
name JSONB NOT NULL, -- 多語系名稱
type TEXT NOT NULL,
location GEOMETRY(Point, 4326) NOT NULL,
-- [新增] 顯示層級控制欄位
display_tier INTEGER DEFAULT 5, -- 1-5 層級
min_zoom_level INTEGER DEFAULT 16, -- 最小顯示 Zoom
daily_passengers INTEGER, -- 每日乘客數 (用於 Tier 4 排序)
-- Hub 聚合相關
is_hub BOOLEAN DEFAULT false,
parent_hub_id UUID REFERENCES nodes(id),
hub_aggregation_radius INTEGER DEFAULT 220, -- 聚合半徑 (公尺)
-- 鐵道公司資訊
operators JSONB, -- ["JR東日本", "東京メトロ"]
primary_operator TEXT, -- 主要經營公司
brand_color TEXT, -- 預先計算的品牌色 (HEX)
-- 其他欄位...
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- 索引優化
CREATE INDEX idx_nodes_display_tier ON nodes(display_tier);
CREATE INDEX idx_nodes_zoom_level ON nodes(min_zoom_level);
CREATE INDEX idx_nodes_parent_hub ON nodes(parent_hub_id);
-- 上野站 (JR 綠色)
UPDATE nodes SET
display_tier = 1,
min_zoom_level = 1,
is_hub = true,
primary_operator = 'JR東日本',
brand_color = '#00AC4E',
operators = '["JR東日本", "東京メトロ"]'::jsonb
WHERE name->>'ja' = '上野駅';
-- 成田機場 (京成藍色)
UPDATE nodes SET
display_tier = 1,
min_zoom_level = 1,
is_hub = true,
primary_operator = '京成電鉄',
brand_color = '#003DA5',
operators = '["京成電鉄", "JR東日本"]'::jsonb
WHERE id = 'odpt:Station:Airport.Narita';
display_tier (1-5)min_zoom_leveloperators 陣列brand_coloris_hub = truebrand_color 欄位useMemo 避免過度計算src/components/map/MapContainer.tsxsrc/components/map/HubNodeLayer.tsxsrc/components/map/L1Layer.tsxsrc/hooks/map/useViewportBounds.ts, src/hooks/map/useVisibleMarkers.tssupabase/migrations/YYYYMMDDHHMMSS_add_display_tier.sql上野 而非 JR上野站)此 Skill 最後更新: 2026-01-24