系列 2:我的 Next.js 16 + OpenLayers + TypeScript 入门套件 — 现代地图应用设置
Source: Dev.to
为什么我再次构建它
几周前,我发布了一个 Next.js + Leaflet 入门套件,作为地图入门套件系列的第一步。完成后,下一个合乎逻辑的部分是 OpenLayers——尤其是针对需要更高级 GIS 工作流的开发者。
Leaflet 适合轻量、交互式地图,但许多 GIS 驱动的项目需要矢量瓦片、自定义投影、高精度几何渲染、先进交互以及对大规模 GeoJSON 数据集的高性能处理。这正是 OpenLayers 的强项。
我从头重新构建了整个入门套件——相同的 UI、相同的结构、相同的开发体验——但使用 OpenLayers 来支持真实的 GIS 用例。这并不是对 Leaflet 版本的替代。OpenLayers 套件发布后,我会完成系列,推出一个 Next.js + MapLibre 入门套件,用于现代矢量瓦片驱动的地图和 3D 可视化。
一个面向生产的 Next.js 16 入门套件,使用原生 OpenLayers 并沿用了 Leaflet 入门套件的 Google‑Maps 风格 UI。
技术栈
| 技术 | 版本 | 用途 |
|---|---|---|
| Next.js | 16 | App Router、Server Components |
| React | 19 | UI 框架 |
| OpenLayers | 10 | 地图(原生,无包装器) |
| TypeScript | 5 | 类型安全 |
| Tailwind CSS | 4 | 样式 |
| shadcn/ui | 最新 | 可访问的 UI 组件 |
功能
- 基础地图设置 – OpenLayers
Map和View的正确初始化 - 多瓦片提供商 – OpenStreetMap、卫星图、暗色模式
- 主题感知瓦片 – 根据亮/暗模式自动切换
- GeoJSON 渲染 – 自定义样式并自适应视野
- 国家搜索 – 防抖搜索并支持键盘导航
- 自定义标记 – 任意位置添加标记并弹出信息框
- 右键菜单 – 右键复制坐标、添加标记、测量
- 测量工具 – 距离和面积的交互式绘制
- POI 管理 – 完整的 CRUD,14 类别,
localStorage持久化 - 地理定位 – 查找用户位置并显示精度圆
- 响应式布局 – 移动抽屉、桌面侧边栏
- 错误边界 – 优雅的错误处理
- 暗色模式 – 完整的主题支持
坐标转换工具
OpenLayers 使用 [lng, lat](Web Mercator 投影),而大多数 API 使用 [lat, lng]。入门套件提供了帮助函数来处理转换:
import { latLngToOL, olToLatLng } from "@/lib/utils/coordinates";
// 将 [lat, lng] 转换为 OpenLayers 坐标
const olCoord = latLngToOL([51.505, -0.09]);
// 再转换回去
const latLng = olToLatLng(olCoord);
正确的图层管理
OpenLayers 需要显式地添加/移除图层。我们提供了 Hook 来简化操作:
const { tileProvider } = useMapTileProvider();
;
防止内存泄漏
OpenLayers 需要手动清理。每个组件都会正确释放资源:
useEffect(() => {
// ... map initialization
return () => {
map.setTarget(undefined); // 从 DOM 中分离
map.dispose(); // 释放资源
};
}, []);
示例组件
地图页面
"use client";
import { MapProvider } from "@/contexts/MapContext";
import { OpenLayersMap, OpenLayersTileLayer } from "@/components/map";
export default function MapPage() {
return (
);
}
GeoJSON 图层
import { OpenLayersGeoJSON } from "@/components/map";
;
地图控件
import { useMapControls } from "@/hooks/useMapControls";
function MapControls() {
const { zoomIn, zoomOut, resetView, flyTo } = useMapControls();
return (
Zoom In
Zoom Out
Reset
flyTo([51.505, -0.09], 13)}>Fly to London
);
}
POI 面板
import { usePOIManager } from "@/hooks/usePOIManager";
function POIPanel() {
const { pois, addPOI, deletePOI, exportGeoJSON } = usePOIManager();
const handleAdd = () => {
addPOI("Coffee Shop", 51.505, -0.09, "food‑drink", "Great espresso");
};
return (
Add POI
Export
{pois.map((poi) => (
{poi.title}
deletePOI(poi.id)}>Delete
))}
);
}
测量面板
import { useMeasurement } from "@/hooks/useMeasurement";
function MeasurementPanel() {
const { startMeasurement, clearMeasurement, distance, area } = useMeasurement();
return (
startMeasurement("distance")}>Measure Distance
startMeasurement("area")}>Measure Area
Clear
{distance > 0 &&
Distance: {(distance / 1000).toFixed(2)} km
}
{area > 0 &&
Area: {(area / 1000000).toFixed(2)} km²
}
);
}
安装
# 克隆仓库
git clone https://github.com/wellywahyudi/nextjs-openlayers-starter.git
cd nextjs-openlayers-starter
# 安装依赖
npm install
# 启动开发服务器
npm run dev
打开 http://localhost:3000/map 即可开始使用。
配置
默认地图设置 (constants/map-config.ts)
export const DEFAULT_MAP_CONFIG: MapConfig = {
defaultCenter: [51.505, -0.09], // [lat, lng]
defaultZoom: 13,
minZoom: 3,
maxZoom: 18,
};
瓦片提供商 (constants/tile-providers.ts)
export const TILE_PROVIDERS: TileProvider[] = [
{
id: "custom",
name: "My Custom Tiles",
url: "https://your-tile-server/{z}/{x}/{y}.png",
attribution: "© Your Attribution",
maxZoom: 19,
category: "standard",
},
// ...existing providers
];
这个入门套件适合谁?
- 🗺️ 构建 GIS 应用(空间分析、自定义投影、矢量瓦片)
- 📊 渲染大规模数据集(10 000+ 要素、复杂几何)
- 🎯 需要高级交互(绘制、编辑、捕捉、测量)
- 🚀 快速原型一个地图密集型应用(仪表盘、分析、可视化)
- 📚 学习 OpenLayers 并想要一个干净、现代的起点
如果你只需要一个带标记的简单地图,Leaflet 版本更简洁。若需要强大灵活的功能,这个 OpenLayers 入门套件是更好的选择。
OpenLayers 细节
- 投影处理 – OpenLayers 默认使用 EPSG:3857(Web Mercator),坐标顺序为
[lng, lat]。入门套件会自动将外部的[lat, lng]输入转换:
// 外部 API 使用 [lat, lng]
const userInput = [51.505, -0.09];
// 转换为 OpenLayers 格式
const olCoord = latLngToOL(userInput);
// 在 OpenLayers 中使用
map.getView().setCenter(olCoord);
-
图层顺序(从下到上):
- 基础瓦片图层
- GeoJSON 矢量图层
- POI 矢量图层
- 标记矢量图层
- 测量矢量图层
-
可树摇的导入:
// ✅ 好 – 可树摇
import Map from "ol/Map";
import View from "ol/View";
import TileLayer from "ol/layer/Tile";
// ❌ 坏 – 导入全部
import * as ol from "ol";
- 打包体积:Leaflet 约 50 KB,OpenLayers 约 90 KB。额外的 GIS 功能让体积的增加是值得的。
路线图
| 入门套件 | 状态 |
|---|---|
| Next.js + Leaflet | ✅ 已可用 |
| Next.js + OpenLayers | ✅ 已可用 |
| Next.js + MapLibre GL | 🚧 计划中 |
相同的 UI、相同的开发体验、不同的地图库——挑选最适合你项目的那一个。
贡献
本项目开源(MIT 许可证)。如果你发现 bug、有什么想法或想要贡献代码:
- 🐛 提交 Issue
- 💡 发起 Discussion
- 🔧 提交 PR
代码库干净、文档完整,适合初学者上手。