feat: Heritage Globe - 中国文物全球分布地图
- Next.js + MapLibre GL 3D 地球 - CARTO dark-matter vector 底图 - 动态星空背景(闪烁、漂移、流星) - 经纬网格线(graticule) - 文物数据图层与朝代/类别筛选 - 中国风信息弹窗 - 中英双语 i18n - LayerPanel + MapControls 浮动面板
This commit is contained in:
+416
@@ -0,0 +1,416 @@
|
||||
# Heritage Globe - 中国文物全球分布地图
|
||||
|
||||
## 项目概述
|
||||
|
||||
基于 OpenGridWorks 技术架构,构建一个展示中国流散文物全球分布的交互式地图系统。
|
||||
|
||||
## 技术栈
|
||||
|
||||
### 核心框架
|
||||
- **Next.js 15** - React 服务端渲染框架
|
||||
- **TypeScript** - 类型安全
|
||||
- **Tailwind CSS** - 样式系统
|
||||
|
||||
### 地图引擎
|
||||
- **MapLibre GL JS 4.x** - 开源矢量地图引擎(Mapbox GL 的开源替代)
|
||||
- **PMTiles** - 云原生瓦片格式,支持按需加载
|
||||
|
||||
### 数据层
|
||||
- **GeoJSON** - 文物坐标数据格式
|
||||
- **Tippecanoe** - GeoJSON → PMTiles 转换工具
|
||||
|
||||
### UI 组件
|
||||
- **Radix UI** - 无障碍组件库(对话框、下拉菜单等)
|
||||
- **Lucide React** - 图标库
|
||||
|
||||
### 国际化
|
||||
- **next-intl** - Next.js 国际化解决方案
|
||||
- 支持中文(简体)/ English 双语切换
|
||||
- URL 路径自动添加语言前缀(/zh、/en)
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
heritage-globe/
|
||||
├── app/ # Next.js App Router
|
||||
│ ├── [locale]/ # 国际化路由
|
||||
│ │ ├── layout.tsx # 语言布局
|
||||
│ │ └── page.tsx # 首页(地图页)
|
||||
│ ├── layout.tsx # 根布局
|
||||
│ └── globals.css # 全局样式
|
||||
├── messages/ # 翻译文件
|
||||
│ ├── zh.json # 中文翻译
|
||||
│ └── en.json # 英文翻译
|
||||
├── components/ # React 组件
|
||||
│ ├── map/ # 地图相关组件
|
||||
│ │ ├── MapContainer.tsx # 地图容器
|
||||
│ │ ├── LayerControl.tsx # 图层控制
|
||||
│ │ └── RelicMarker.tsx # 文物标记
|
||||
│ ├── sidebar/ # 侧边栏组件
|
||||
│ │ ├── Sidebar.tsx # 侧边栏容器
|
||||
│ │ ├── FilterPanel.tsx # 筛选面板
|
||||
│ │ └── SearchBox.tsx # 搜索框
|
||||
│ └── ui/ # 通用 UI 组件
|
||||
├── lib/ # 工具函数
|
||||
│ ├── map-utils.ts # 地图工具函数
|
||||
│ ├── pmtiles-loader.ts # PMTiles 加载器
|
||||
│ └── data-processor.ts # 数据处理
|
||||
├── public/ # 静态资源
|
||||
│ ├── tiles/ # PMTiles 瓦片文件
|
||||
│ └── icons/ # 自定义图标
|
||||
├── data/ # 原始数据
|
||||
│ └── relics.geojson # 文物 GeoJSON 数据
|
||||
└── types/ # TypeScript 类型定义
|
||||
└── relic.ts
|
||||
```
|
||||
|
||||
## 搭建步骤
|
||||
|
||||
### 阶段 1: 项目初始化
|
||||
|
||||
```bash
|
||||
# 创建 Next.js 项目
|
||||
npx create-next-app@latest . --typescript --tailwind --app --no-src-dir --import-alias "@/*"
|
||||
|
||||
# 安装核心依赖
|
||||
npm install maplibre-gl pmtiles
|
||||
|
||||
# 安装 UI 组件
|
||||
npm install @radix-ui/react-dialog @radix-ui/react-select @radix-ui/react-slider
|
||||
npm install lucide-react
|
||||
|
||||
# 安装国际化
|
||||
npm install next-intl
|
||||
|
||||
# 安装开发工具
|
||||
npm install -D @types/geojson
|
||||
```
|
||||
|
||||
### 阶段 2: 国际化配置
|
||||
|
||||
**2.1 创建翻译文件**
|
||||
|
||||
`messages/zh.json`:
|
||||
```json
|
||||
{
|
||||
"nav": {
|
||||
"title": "中国文物全球分布",
|
||||
"search": "搜索文物或博物馆"
|
||||
},
|
||||
"map": {
|
||||
"layers": "图层",
|
||||
"filters": "筛选器",
|
||||
"reset": "重置"
|
||||
},
|
||||
"dynasty": {
|
||||
"shang": "商周",
|
||||
"qin": "秦汉",
|
||||
"tang": "隋唐",
|
||||
"song": "宋元",
|
||||
"ming": "明清"
|
||||
},
|
||||
"category": {
|
||||
"bronze": "青铜器",
|
||||
"porcelain": "瓷器",
|
||||
"painting": "书画",
|
||||
"jade": "玉器"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`messages/en.json`:
|
||||
```json
|
||||
{
|
||||
"nav": {
|
||||
"title": "Chinese Cultural Relics Worldwide",
|
||||
"search": "Search relics or museums"
|
||||
},
|
||||
"map": {
|
||||
"layers": "Layers",
|
||||
"filters": "Filters",
|
||||
"reset": "Reset"
|
||||
},
|
||||
"dynasty": {
|
||||
"shang": "Shang-Zhou",
|
||||
"qin": "Qin-Han",
|
||||
"tang": "Sui-Tang",
|
||||
"song": "Song-Yuan",
|
||||
"ming": "Ming-Qing"
|
||||
},
|
||||
"category": {
|
||||
"bronze": "Bronze",
|
||||
"porcelain": "Porcelain",
|
||||
"painting": "Painting",
|
||||
"jade": "Jade"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**2.2 配置 next-intl**
|
||||
|
||||
`i18n.ts`:
|
||||
```typescript
|
||||
import { getRequestConfig } from 'next-intl/server';
|
||||
|
||||
export default getRequestConfig(async ({ locale }) => ({
|
||||
messages: (await import(`./messages/${locale}.json`)).default
|
||||
}));
|
||||
```
|
||||
|
||||
`middleware.ts`:
|
||||
```typescript
|
||||
import createMiddleware from 'next-intl/middleware';
|
||||
|
||||
export default createMiddleware({
|
||||
locales: ['zh', 'en'],
|
||||
defaultLocale: 'zh',
|
||||
localeDetection: true
|
||||
});
|
||||
|
||||
export const config = {
|
||||
matcher: ['/', '/(zh|en)/:path*']
|
||||
};
|
||||
```
|
||||
|
||||
### 阶段 3: 地图基础设施
|
||||
|
||||
**3.1 配置 MapLibre GL CSS**
|
||||
- 在 `app/[locale]/layout.tsx` 中引入 `maplibre-gl/dist/maplibre-gl.css`
|
||||
|
||||
**3.2 创建地图容器组件**
|
||||
- `components/map/MapContainer.tsx`
|
||||
- 初始化 MapLibre 地图实例
|
||||
- 设置中国为初始中心点(北京:116.4074, 39.9042)
|
||||
- 使用 Carto 深色底图
|
||||
|
||||
**3.3 创建语言切换器**
|
||||
```typescript
|
||||
// components/LanguageSwitcher.tsx
|
||||
'use client';
|
||||
import { useLocale } from 'next-intl';
|
||||
import { useRouter, usePathname } from 'next/navigation';
|
||||
|
||||
export function LanguageSwitcher() {
|
||||
const locale = useLocale();
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
|
||||
const toggleLanguage = () => {
|
||||
const newLocale = locale === 'zh' ? 'en' : 'zh';
|
||||
router.push(pathname.replace(`/${locale}`, `/${newLocale}`));
|
||||
};
|
||||
|
||||
return (
|
||||
<button onClick={toggleLanguage}>
|
||||
{locale === 'zh' ? 'EN' : '中文'}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**3.4 集成 PMTiles**
|
||||
- `lib/pmtiles-loader.ts`
|
||||
- 实现 PMTiles Protocol 注册
|
||||
- 支持本地和远程瓦片加载
|
||||
|
||||
### 阶段 4: 数据层
|
||||
|
||||
**4.1 定义数据结构(支持双语)**
|
||||
```typescript
|
||||
// types/relic.ts
|
||||
interface Relic {
|
||||
id: string;
|
||||
name: {
|
||||
zh: string; // 中文名称
|
||||
en: string; // 英文名称
|
||||
};
|
||||
dynasty: string; // 朝代(使用 key,如 "tang")
|
||||
category: string; // 类别(使用 key,如 "bronze")
|
||||
currentLocation: {
|
||||
zh: string; // 中文地点
|
||||
en: string; // 英文地点
|
||||
};
|
||||
museum: {
|
||||
zh: string; // 中文机构名
|
||||
en: string; // 英文机构名
|
||||
};
|
||||
coordinates: [number, number]; // 经纬度
|
||||
year?: string; // 年代
|
||||
description?: {
|
||||
zh: string;
|
||||
en: string;
|
||||
};
|
||||
imageUrl?: string; // 图片 URL
|
||||
protectionLevel?: string; // 保护级别
|
||||
}
|
||||
```
|
||||
|
||||
**4.2 准备示例数据(双语)**
|
||||
- 创建 `data/relics.geojson`
|
||||
- 至少包含 10-20 个示例文物点位
|
||||
- 覆盖英国、法国、美国、日本等主要国家
|
||||
|
||||
**4.3 生成 PMTiles**
|
||||
```bash
|
||||
# 安装 tippecanoe(macOS)
|
||||
brew install tippecanoe
|
||||
|
||||
# 转换 GeoJSON → PMTiles
|
||||
tippecanoe -o public/tiles/relics.pmtiles \
|
||||
--minimum-zoom=0 \
|
||||
--maximum-zoom=14 \
|
||||
--drop-densest-as-needed \
|
||||
--extend-zooms-if-still-dropping \
|
||||
data/relics.geojson
|
||||
```
|
||||
|
||||
### 阶段 5: 图层系统
|
||||
|
||||
**4.1 朝代图层**
|
||||
- 根据朝代(商周、秦汉、隋唐、宋元、明清等)分层
|
||||
- 不同朝代使用不同颜色标识
|
||||
- 支持单独开关
|
||||
|
||||
**4.2 类别图层**
|
||||
- 青铜器、瓷器、书画、玉器、石刻等
|
||||
- 使用不同图标表示
|
||||
|
||||
**4.3 气泡样式**
|
||||
- 根据文物重要性(保护级别)调整大小
|
||||
- 聚类显示(缩小时自动聚合)
|
||||
|
||||
### 阶段 6: 交互功能
|
||||
|
||||
**5.1 侧边栏控制面板**
|
||||
- 图层树形结构(类似 OpenGridWorks)
|
||||
- 拖拽排序
|
||||
- 开关动画
|
||||
|
||||
**5.2 筛选器**
|
||||
- 朝代范围滑块
|
||||
- 类别多选
|
||||
- 保护级别筛选
|
||||
- 国家/地区筛选
|
||||
|
||||
**5.3 搜索功能**
|
||||
- 文物名称模糊搜索
|
||||
- 收藏机构搜索
|
||||
- 搜索结果定位到地图
|
||||
|
||||
**5.4 详情弹窗**
|
||||
- 点击文物标记显示详情卡片
|
||||
- 展示图片、名称、年代、描述等
|
||||
- 提供外部链接(如博物馆官网)
|
||||
|
||||
### 阶段 7: 视觉优化
|
||||
|
||||
**6.1 主题样式**
|
||||
- 采用深色主题(类似 OpenGridWorks)
|
||||
- 定义 CSS 变量统一配色
|
||||
|
||||
**6.2 响应式设计**
|
||||
- 桌面端:侧边栏固定
|
||||
- 移动端:抽屉式侧边栏
|
||||
|
||||
**6.3 动画效果**
|
||||
- 图层切换动画
|
||||
- 标记 hover 效果
|
||||
- 平滑缩放过渡
|
||||
|
||||
### 阶段 8: 高级功能(可选)
|
||||
|
||||
**7.1 时间轴**
|
||||
- 按文物流失年代播放动画
|
||||
- 展示流散历程
|
||||
|
||||
**7.2 统计面板**
|
||||
- 总文物数量
|
||||
- 分布国家统计
|
||||
- 朝代占比图表
|
||||
|
||||
**7.3 分享功能**
|
||||
- 生成带参数的分享链接
|
||||
- 截图导出
|
||||
|
||||
**7.4 预设视图**
|
||||
- "丝绸之路文物"
|
||||
- "战争掠夺文物"
|
||||
- "敦煌流散文物"
|
||||
|
||||
## 核心配置文件
|
||||
|
||||
### `next.config.js`
|
||||
```javascript
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
webpack: (config) => {
|
||||
// 支持 PMTiles 二进制文件
|
||||
config.module.rules.push({
|
||||
test: /\.pmtiles$/,
|
||||
type: 'asset/resource',
|
||||
});
|
||||
return config;
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
```
|
||||
|
||||
### `tailwind.config.ts`
|
||||
```typescript
|
||||
import type { Config } from 'tailwindcss';
|
||||
|
||||
const config: Config = {
|
||||
darkMode: 'class',
|
||||
content: [
|
||||
'./app/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
'./components/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
map: {
|
||||
bg: '#0a0e27',
|
||||
panel: '#1a1e3a',
|
||||
accent: '#3b82f6',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
|
||||
export default config;
|
||||
```
|
||||
|
||||
## 开发流程
|
||||
|
||||
1. **初始化项目** → 安装依赖
|
||||
2. **搭建地图** → 验证 MapLibre 正常渲染
|
||||
3. **加载数据** → 展示示例文物点位
|
||||
4. **实现图层** → 按朝代/类别分层
|
||||
5. **添加交互** → 点击、筛选、搜索
|
||||
6. **优化样式** → 深色主题、响应式
|
||||
7. **测试部署** → Vercel 一键部署
|
||||
|
||||
## 数据来源建议
|
||||
|
||||
- **公开数据集**: UNESCO、大英博物馆、卢浮宫等开放数据
|
||||
- **研究报告**: 流失文物统计报告
|
||||
- **众包贡献**: 社区上传补充
|
||||
|
||||
## 参考资源
|
||||
|
||||
- [MapLibre GL JS 文档](https://maplibre.org/maplibre-gl-js/docs/)
|
||||
- [PMTiles 规范](https://github.com/protomaps/PMTiles)
|
||||
- [OpenGridWorks](https://opengridworks.com) - 参考实现
|
||||
- [GeoJSON 规范](https://geojson.org/)
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT License - 开源友好
|
||||
|
||||
---
|
||||
|
||||
**预计开发时间**: 2-3 天(核心功能)
|
||||
**扩展功能**: 根据需求迭代
|
||||
Reference in New Issue
Block a user