第02章 - 环境搭建与快速开始
第02章 - 环境搭建与快速开始
2.1 开发环境准备
2.1.1 系统要求
开发 OpenLayers 应用需要以下基本环境:
操作系统:
- Windows 10/11
- macOS 10.15+
- Linux(Ubuntu 20.04+、CentOS 8+)
必备软件:
| 软件 | 版本要求 | 说明 |
|---|---|---|
| Node.js | 18.0+ | JavaScript 运行环境 |
| npm/yarn/pnpm | 最新版 | 包管理器 |
| Git | 2.30+ | 版本控制 |
| 现代浏览器 | Chrome 90+ / Firefox 90+ | 开发调试 |
推荐 IDE:
- Visual Studio Code(推荐)
- WebStorm
- Sublime Text
2.1.2 Node.js 安装
Windows 安装:
# 使用 nvm-windows 管理 Node.js 版本
# 下载地址:https://github.com/coreybutler/nvm-windows/releases
# 安装 Node.js LTS 版本
nvm install lts
nvm use lts
# 验证安装
node --version
npm --version
macOS 安装:
# 使用 Homebrew 安装 nvm
brew install nvm
# 配置环境变量
echo 'export NVM_DIR="$HOME/.nvm"' >> ~/.zshrc
echo '[ -s "/opt/homebrew/opt/nvm/nvm.sh" ] && \. "/opt/homebrew/opt/nvm/nvm.sh"' >> ~/.zshrc
# 安装 Node.js
nvm install --lts
nvm use --lts
# 验证安装
node --version
npm --version
Linux 安装:
# 安装 nvm
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
# 重新加载配置
source ~/.bashrc
# 安装 Node.js
nvm install --lts
nvm use --lts
# 验证安装
node --version
npm --version
2.1.3 VS Code 配置
推荐扩展:
// .vscode/extensions.json
{
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"ms-vscode.vscode-typescript-next",
"bradlc.vscode-tailwindcss",
"christian-kohler.path-intellisense",
"formulahendry.auto-rename-tag",
"streetsidesoftware.code-spell-checker"
]
}
工作区设置:
// .vscode/settings.json
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.tabSize": 2,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"typescript.preferences.importModuleSpecifier": "relative",
"emmet.includeLanguages": {
"javascript": "javascriptreact"
}
}
2.2 项目初始化
2.2.1 使用 Vite 创建项目
创建 Vanilla JavaScript 项目:
# 创建项目目录
npm create vite@latest openlayers-demo -- --template vanilla
# 进入项目目录
cd openlayers-demo
# 安装 OpenLayers
npm install ol
# 安装开发依赖
npm install
# 启动开发服务器
npm run dev
项目结构:
openlayers-demo/
├── node_modules/
├── public/
│ └── vite.svg
├── src/
│ ├── main.js
│ └── style.css
├── index.html
├── package.json
├── vite.config.js
└── README.md
2.2.2 使用 Vue 3 创建项目
# 创建 Vue 项目
npm create vite@latest openlayers-vue -- --template vue
# 进入项目目录
cd openlayers-vue
# 安装 OpenLayers
npm install ol
# 安装 vue3-openlayers(可选的 Vue 封装)
npm install vue3-openlayers
# 启动开发服务器
npm run dev
Vue 组件示例:
<!-- src/components/MapView.vue -->
<template>
<div ref="mapContainer" class="map-container"></div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import Map from 'ol/Map';
import View from 'ol/View';
import TileLayer from 'ol/layer/Tile';
import OSM from 'ol/source/OSM';
import { fromLonLat } from 'ol/proj';
import 'ol/ol.css';
const mapContainer = ref(null);
let map = null;
onMounted(() => {
map = new Map({
target: mapContainer.value,
layers: [
new TileLayer({
source: new OSM()
})
],
view: new View({
center: fromLonLat([116.4074, 39.9042]),
zoom: 10
})
});
});
onUnmounted(() => {
if (map) {
map.setTarget(null);
map = null;
}
});
</script>
<style scoped>
.map-container {
width: 100%;
height: 100vh;
}
</style>
2.2.3 使用 React 创建项目
# 创建 React 项目
npm create vite@latest openlayers-react -- --template react
# 进入项目目录
cd openlayers-react
# 安装 OpenLayers
npm install ol
# 启动开发服务器
npm run dev
React 组件示例:
// src/components/MapView.jsx
import { useEffect, useRef } from 'react';
import Map from 'ol/Map';
import View from 'ol/View';
import TileLayer from 'ol/layer/Tile';
import OSM from 'ol/source/OSM';
import { fromLonLat } from 'ol/proj';
import 'ol/ol.css';
function MapView() {
const mapRef = useRef(null);
const mapInstanceRef = useRef(null);
useEffect(() => {
if (mapRef.current && !mapInstanceRef.current) {
mapInstanceRef.current = new Map({
target: mapRef.current,
layers: [
new TileLayer({
source: new OSM()
})
],
view: new View({
center: fromLonLat([116.4074, 39.9042]),
zoom: 10
})
});
}
return () => {
if (mapInstanceRef.current) {
mapInstanceRef.current.setTarget(null);
mapInstanceRef.current = null;
}
};
}, []);
return (
<div
ref={mapRef}
style={{ width: '100%', height: '100vh' }}
/>
);
}
export default MapView;
2.2.4 TypeScript 项目配置
# 创建 TypeScript 项目
npm create vite@latest openlayers-ts -- --template vanilla-ts
cd openlayers-ts
# 安装 OpenLayers
npm install ol
# OpenLayers 自带类型定义,无需额外安装
npm run dev
TypeScript 配置:
// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"]
}
TypeScript 示例:
// src/main.ts
import 'ol/ol.css';
import Map from 'ol/Map';
import View from 'ol/View';
import TileLayer from 'ol/layer/Tile';
import OSM from 'ol/source/OSM';
import { fromLonLat } from 'ol/proj';
import type { Coordinate } from 'ol/coordinate';
// 定义地图配置类型
interface MapConfig {
center: Coordinate;
zoom: number;
}
const config: MapConfig = {
center: fromLonLat([116.4074, 39.9042]),
zoom: 10
};
const map = new Map({
target: 'map',
layers: [
new TileLayer({
source: new OSM()
})
],
view: new View({
center: config.center,
zoom: config.zoom
})
});
// 类型安全的事件处理
map.on('click', (event) => {
const coordinate = event.coordinate;
console.log('点击坐标:', coordinate);
});
2.3 常用底图配置
2.3.1 OpenStreetMap
import TileLayer from 'ol/layer/Tile';
import OSM from 'ol/source/OSM';
// 默认 OSM
const osmLayer = new TileLayer({
source: new OSM()
});
// 自定义 OSM 服务器
const customOSM = new TileLayer({
source: new OSM({
url: 'https://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png',
maxZoom: 19
})
});
2.3.2 天地图
import TileLayer from 'ol/layer/Tile';
import XYZ from 'ol/source/XYZ';
// 天地图 key(需要注册获取)
const tdtKey = 'YOUR_TIANDITU_KEY';
// 天地图矢量底图
const tdtVec = new TileLayer({
source: new XYZ({
url: `http://t{0-7}.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${tdtKey}`,
maxZoom: 18
})
});
// 天地图矢量注记
const tdtCva = new TileLayer({
source: new XYZ({
url: `http://t{0-7}.tianditu.gov.cn/cva_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${tdtKey}`,
maxZoom: 18
})
});
// 天地图影像底图
const tdtImg = new TileLayer({
source: new XYZ({
url: `http://t{0-7}.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${tdtKey}`,
maxZoom: 18
})
});
// 天地图影像注记
const tdtCia = new TileLayer({
source: new XYZ({
url: `http://t{0-7}.tianditu.gov.cn/cia_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cia&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${tdtKey}`,
maxZoom: 18
})
});
2.3.3 高德地图
import TileLayer from 'ol/layer/Tile';
import XYZ from 'ol/source/XYZ';
// 高德矢量底图
const gaodeVec = new TileLayer({
source: new XYZ({
url: 'https://webrd0{1-4}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}',
maxZoom: 18
})
});
// 高德影像底图
const gaodeImg = new TileLayer({
source: new XYZ({
url: 'https://webst0{1-4}.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}',
maxZoom: 18
})
});
// 高德影像注记
const gaodeAnn = new TileLayer({
source: new XYZ({
url: 'https://webst0{1-4}.is.autonavi.com/appmaptile?style=8&x={x}&y={y}&z={z}',
maxZoom: 18
})
});
2.3.4 百度地图(需坐标转换)
import TileLayer from 'ol/layer/Tile';
import TileImage from 'ol/source/TileImage';
import TileGrid from 'ol/tilegrid/TileGrid';
import { get as getProjection } from 'ol/proj';
// 百度地图使用自定义投影,需要特殊处理
// 创建百度地图瓦片网格
const resolutions = [];
for (let i = 0; i < 19; i++) {
resolutions[i] = Math.pow(2, 18 - i);
}
const baiduTileGrid = new TileGrid({
origin: [0, 0],
resolutions: resolutions
});
// 百度地图图层
const baiduLayer = new TileLayer({
source: new TileImage({
projection: getProjection('EPSG:3857'),
tileGrid: baiduTileGrid,
tileUrlFunction: function(tileCoord) {
if (!tileCoord) return '';
const z = tileCoord[0];
const x = tileCoord[1];
const y = -tileCoord[2] - 1;
// 百度地图服务器随机选择
const num = Math.abs((x + y) % 10);
return `http://online${num}.map.bdimg.com/tile/?qt=tile&x=${x}&y=${y}&z=${z}&styles=pl&udt=20170601`;
}
})
});
2.3.5 必应地图
import TileLayer from 'ol/layer/Tile';
import BingMaps from 'ol/source/BingMaps';
// 必应地图(需要 API Key)
const bingKey = 'YOUR_BING_MAPS_KEY';
// 道路地图
const bingRoad = new TileLayer({
source: new BingMaps({
key: bingKey,
imagerySet: 'Road'
})
});
// 航空影像
const bingAerial = new TileLayer({
source: new BingMaps({
key: bingKey,
imagerySet: 'Aerial'
})
});
// 航空影像带标签
const bingAerialLabel = new TileLayer({
source: new BingMaps({
key: bingKey,
imagerySet: 'AerialWithLabels'
})
});
2.3.6 ArcGIS 在线服务
import TileLayer from 'ol/layer/Tile';
import XYZ from 'ol/source/XYZ';
// ArcGIS 世界街道地图
const arcgisStreet = new TileLayer({
source: new XYZ({
url: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}',
maxZoom: 19
})
});
// ArcGIS 世界影像
const arcgisImagery = new TileLayer({
source: new XYZ({
url: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
maxZoom: 19
})
});
// ArcGIS 世界地形
const arcgisTerrain = new TileLayer({
source: new XYZ({
url: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Terrain_Base/MapServer/tile/{z}/{y}/{x}',
maxZoom: 13
})
});
2.3.7 底图切换实现
import Map from 'ol/Map';
import View from 'ol/View';
import TileLayer from 'ol/layer/Tile';
import OSM from 'ol/source/OSM';
import XYZ from 'ol/source/XYZ';
import { fromLonLat } from 'ol/proj';
// 定义底图配置
const basemaps = {
osm: new TileLayer({
source: new OSM(),
visible: true
}),
gaode: new TileLayer({
source: new XYZ({
url: 'https://webrd0{1-4}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}'
}),
visible: false
}),
arcgis: new TileLayer({
source: new XYZ({
url: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}'
}),
visible: false
})
};
// 创建地图
const map = new Map({
target: 'map',
layers: Object.values(basemaps),
view: new View({
center: fromLonLat([116.4074, 39.9042]),
zoom: 10
})
});
// 切换底图函数
function switchBasemap(name) {
Object.keys(basemaps).forEach(key => {
basemaps[key].setVisible(key === name);
});
}
// 使用示例
document.getElementById('osm-btn').addEventListener('click', () => switchBasemap('osm'));
document.getElementById('gaode-btn').addEventListener('click', () => switchBasemap('gaode'));
document.getElementById('arcgis-btn').addEventListener('click', () => switchBasemap('arcgis'));
2.4 项目配置优化
2.4.1 Vite 配置
// vite.config.js
import { defineConfig } from 'vite';
export default defineConfig({
// 开发服务器配置
server: {
port: 3000,
open: true,
cors: true,
proxy: {
// 代理 GeoServer 请求
'/geoserver': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
},
// 构建配置
build: {
outDir: 'dist',
sourcemap: true,
minify: 'terser',
rollupOptions: {
output: {
manualChunks: {
// 将 OpenLayers 单独打包
ol: ['ol']
}
}
}
},
// 优化配置
optimizeDeps: {
include: ['ol']
}
});
2.4.2 ESLint 配置
// eslint.config.js
import js from '@eslint/js';
import globals from 'globals';
export default [
js.configs.recommended,
{
languageOptions: {
globals: {
...globals.browser,
...globals.es2021
},
ecmaVersion: 'latest',
sourceType: 'module'
},
rules: {
'no-unused-vars': 'warn',
'no-console': 'off',
'semi': ['error', 'always'],
'quotes': ['error', 'single']
}
}
];
2.4.3 Prettier 配置
// .prettierrc
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 100,
"bracketSpacing": true,
"arrowParens": "avoid"
}
2.4.4 环境变量配置
# .env.development
VITE_MAP_CENTER_LON=116.4074
VITE_MAP_CENTER_LAT=39.9042
VITE_MAP_ZOOM=10
VITE_GEOSERVER_URL=http://localhost:8080/geoserver
VITE_TIANDITU_KEY=your_tianditu_key
# .env.production
VITE_MAP_CENTER_LON=116.4074
VITE_MAP_CENTER_LAT=39.9042
VITE_MAP_ZOOM=10
VITE_GEOSERVER_URL=https://geoserver.example.com/geoserver
VITE_TIANDITU_KEY=your_tianditu_key
// 使用环境变量
import { fromLonLat } from 'ol/proj';
const center = fromLonLat([
parseFloat(import.meta.env.VITE_MAP_CENTER_LON),
parseFloat(import.meta.env.VITE_MAP_CENTER_LAT)
]);
const zoom = parseInt(import.meta.env.VITE_MAP_ZOOM);
const geoserverUrl = import.meta.env.VITE_GEOSERVER_URL;
2.5 调试技巧
2.5.1 浏览器开发者工具
// 将地图实例挂载到全局,方便调试
window.map = map;
window.ol = ol;
// 在控制台中可以执行
// map.getView().getCenter()
// map.getLayers().getArray()
// map.getView().setZoom(15)
2.5.2 添加调试图层
import VectorLayer from 'ol/layer/Vector';
import VectorSource from 'ol/source/Vector';
import { Style, Stroke, Circle, Fill } from 'ol/style';
// 创建调试图层
const debugLayer = new VectorLayer({
source: new VectorSource(),
style: new Style({
stroke: new Stroke({
color: 'red',
width: 2
}),
image: new Circle({
radius: 5,
fill: new Fill({ color: 'red' })
})
}),
zIndex: 999
});
map.addLayer(debugLayer);
// 点击时添加调试点
map.on('click', (event) => {
const feature = new Feature({
geometry: new Point(event.coordinate)
});
debugLayer.getSource().addFeature(feature);
console.log('点击坐标:', event.coordinate);
});
2.5.3 地图状态监控
// 监控地图状态变化
map.on('moveend', () => {
const view = map.getView();
console.log('地图状态:', {
center: view.getCenter(),
zoom: view.getZoom(),
resolution: view.getResolution(),
extent: view.calculateExtent(map.getSize())
});
});
// 监控图层加载
map.getLayers().forEach(layer => {
if (layer.getSource && layer.getSource().on) {
layer.getSource().on('tileloadstart', () => {
console.log('瓦片开始加载');
});
layer.getSource().on('tileloadend', () => {
console.log('瓦片加载完成');
});
layer.getSource().on('tileloaderror', (event) => {
console.error('瓦片加载失败:', event);
});
}
});
2.5.4 性能监控
// 帧率监控
let frameCount = 0;
let lastTime = performance.now();
map.on('postrender', () => {
frameCount++;
const currentTime = performance.now();
if (currentTime - lastTime >= 1000) {
console.log(`FPS: ${frameCount}`);
frameCount = 0;
lastTime = currentTime;
}
});
// 渲染时间监控
map.on('precompose', () => {
console.time('render');
});
map.on('postcompose', () => {
console.timeEnd('render');
});
2.6 完整示例项目
2.6.1 项目结构
openlayers-quickstart/
├── public/
│ └── favicon.ico
├── src/
│ ├── components/
│ │ ├── MapView.js
│ │ └── LayerSwitcher.js
│ ├── config/
│ │ └── basemaps.js
│ ├── utils/
│ │ └── mapUtils.js
│ ├── styles/
│ │ └── map.css
│ ├── main.js
│ └── style.css
├── index.html
├── package.json
├── vite.config.js
└── README.md
2.6.2 配置文件
// package.json
{
"name": "openlayers-quickstart",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"ol": "^9.2.4"
},
"devDependencies": {
"vite": "^5.2.0"
}
}
2.6.3 核心代码
// src/config/basemaps.js
import TileLayer from 'ol/layer/Tile';
import OSM from 'ol/source/OSM';
import XYZ from 'ol/source/XYZ';
export const basemapConfigs = {
osm: {
name: 'OpenStreetMap',
layer: new TileLayer({
source: new OSM(),
properties: { name: 'osm' }
})
},
gaode: {
name: '高德地图',
layer: new TileLayer({
source: new XYZ({
url: 'https://webrd0{1-4}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}'
}),
properties: { name: 'gaode' }
})
},
satellite: {
name: '卫星影像',
layer: new TileLayer({
source: new XYZ({
url: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}'
}),
properties: { name: 'satellite' }
})
}
};
export function getBasemapLayers() {
return Object.values(basemapConfigs).map(config => config.layer);
}
// src/utils/mapUtils.js
import { fromLonLat, toLonLat } from 'ol/proj';
// 坐标转换工具
export function lonLatToWebMercator(lon, lat) {
return fromLonLat([lon, lat]);
}
export function webMercatorToLonLat(x, y) {
return toLonLat([x, y]);
}
// 格式化坐标显示
export function formatCoordinate(coordinate, precision = 6) {
const lonLat = toLonLat(coordinate);
return `经度: ${lonLat[0].toFixed(precision)}, 纬度: ${lonLat[1].toFixed(precision)}`;
}
// 计算两点距离(米)
export function calculateDistance(coord1, coord2) {
const [lon1, lat1] = toLonLat(coord1);
const [lon2, lat2] = toLonLat(coord2);
const R = 6371000; // 地球半径(米)
const dLat = (lat2 - lat1) * Math.PI / 180;
const dLon = (lon2 - lon1) * Math.PI / 180;
const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
Math.sin(dLon / 2) * Math.sin(dLon / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c;
}
// src/components/MapView.js
import Map from 'ol/Map';
import View from 'ol/View';
import { defaults as defaultControls, ScaleLine, MousePosition } from 'ol/control';
import { createStringXY } from 'ol/coordinate';
import { fromLonLat } from 'ol/proj';
import { getBasemapLayers } from '../config/basemaps.js';
export function createMap(targetId, options = {}) {
const {
center = [116.4074, 39.9042],
zoom = 10,
minZoom = 2,
maxZoom = 18
} = options;
// 创建控件
const controls = defaultControls().extend([
new ScaleLine({
units: 'metric'
}),
new MousePosition({
coordinateFormat: createStringXY(6),
projection: 'EPSG:4326',
className: 'mouse-position'
})
]);
// 创建地图
const map = new Map({
target: targetId,
layers: getBasemapLayers(),
view: new View({
center: fromLonLat(center),
zoom: zoom,
minZoom: minZoom,
maxZoom: maxZoom
}),
controls: controls
});
// 默认只显示第一个底图
const layers = map.getLayers().getArray();
layers.forEach((layer, index) => {
layer.setVisible(index === 0);
});
return map;
}
export function switchBasemap(map, basemapName) {
const layers = map.getLayers().getArray();
layers.forEach(layer => {
const name = layer.get('name');
if (name) {
layer.setVisible(name === basemapName);
}
});
}
// src/components/LayerSwitcher.js
import { basemapConfigs } from '../config/basemaps.js';
import { switchBasemap } from './MapView.js';
export function createLayerSwitcher(map, containerId) {
const container = document.getElementById(containerId);
if (!container) {
console.error('Layer switcher container not found');
return;
}
// 创建底图切换按钮
Object.entries(basemapConfigs).forEach(([key, config]) => {
const button = document.createElement('button');
button.textContent = config.name;
button.className = 'basemap-btn';
button.dataset.basemap = key;
button.addEventListener('click', () => {
switchBasemap(map, key);
// 更新按钮状态
container.querySelectorAll('.basemap-btn').forEach(btn => {
btn.classList.remove('active');
});
button.classList.add('active');
});
container.appendChild(button);
});
// 默认选中第一个
container.querySelector('.basemap-btn')?.classList.add('active');
}
// src/main.js
import 'ol/ol.css';
import './styles/map.css';
import { createMap } from './components/MapView.js';
import { createLayerSwitcher } from './components/LayerSwitcher.js';
import { formatCoordinate } from './utils/mapUtils.js';
// 创建地图
const map = createMap('map', {
center: [116.4074, 39.9042],
zoom: 10
});
// 创建底图切换器
createLayerSwitcher(map, 'layer-switcher');
// 添加点击事件
map.on('click', (event) => {
console.log('点击位置:', formatCoordinate(event.coordinate));
});
// 将地图实例挂载到全局(调试用)
window.map = map;
console.log('地图初始化完成');
/* src/styles/map.css */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
#map {
width: 100%;
height: 100vh;
}
#layer-switcher {
position: absolute;
top: 10px;
right: 10px;
z-index: 1000;
background: white;
padding: 10px;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
.basemap-btn {
display: block;
width: 100%;
padding: 8px 16px;
margin-bottom: 5px;
border: 1px solid #ddd;
border-radius: 4px;
background: white;
cursor: pointer;
transition: all 0.3s;
}
.basemap-btn:last-child {
margin-bottom: 0;
}
.basemap-btn:hover {
background: #f5f5f5;
}
.basemap-btn.active {
background: #1890ff;
color: white;
border-color: #1890ff;
}
.mouse-position {
position: absolute;
bottom: 30px;
left: 10px;
background: rgba(255, 255, 255, 0.9);
padding: 5px 10px;
border-radius: 4px;
font-size: 12px;
}
.ol-scale-line {
left: auto;
right: 10px;
bottom: 10px;
}
<!-- index.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OpenLayers 快速入门</title>
</head>
<body>
<div id="map"></div>
<div id="layer-switcher"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
2.7 本章小结
本章详细介绍了 OpenLayers 开发环境的搭建:
- 开发环境准备:Node.js、VS Code 配置
- 项目初始化:Vite、Vue、React、TypeScript 项目创建
- 常用底图配置:OSM、天地图、高德、必应、ArcGIS
- 项目配置优化:Vite、ESLint、Prettier、环境变量
- 调试技巧:浏览器工具、调试图层、状态监控
- 完整示例:项目结构和核心代码
关键要点
- 推荐使用 Vite 作为构建工具
- OpenLayers 自带 TypeScript 类型定义
- 国内地图服务需要申请 API Key
- 将地图实例挂载到全局便于调试
- 合理使用环境变量管理配置
下一步
在下一章中,我们将深入学习 OpenLayers 的核心概念与架构设计,包括:
- 核心对象模型
- 事件系统
- 可观察模式
- 模块化设计

浙公网安备 33010602011771号