系列 2:我的 Next.js 16 + OpenLayers + TypeScript 入门套件 — 现代地图应用设置

发布: (2025年12月5日 GMT+8 23:58)
7 min read
原文: Dev.to

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.js16App Router、Server Components
React19UI 框架
OpenLayers10地图(原生,无包装器)
TypeScript5类型安全
Tailwind CSS4样式
shadcn/ui最新可访问的 UI 组件

功能

  • 基础地图设置 – OpenLayers MapView 的正确初始化
  • 多瓦片提供商 – 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);
  • 图层顺序(从下到上):

    1. 基础瓦片图层
    2. GeoJSON 矢量图层
    3. POI 矢量图层
    4. 标记矢量图层
    5. 测量矢量图层
  • 可树摇的导入:

// ✅ 好 – 可树摇
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

代码库干净、文档完整,适合初学者上手。

Back to Blog

相关文章

阅读更多 »

AI 驱动开发平台

🤔 让我彻夜难眠的问题 想象一下:你在 GitHub 上发现了一个超棒的开源项目。它有 10,000 多个 issue,数百名贡献者,……