164.在 Vue3 中使用 OpenLayers 加載 Esri 地圖(多種形式)

適配:Vue 3 + Vite + TypeScript(也兼容 JS)
地圖引擎:OpenLayers v10+
目標:一次性學會 多種 Esri 底圖加載方式注記疊加動態切換令牌(Token)鑒權常見坑位排查


一、效果預覽


二、為什么選 OpenLayers + Esri

  • OpenLayers:開源、功能強、國產項目生態友好,坐標系與投影支持完善;

  • Esri Basemaps:樣式豐富、全球覆蓋、質量高(影像、街道、灰底、地形、海洋等);

  • 開箱即用的 XYZ:Esri 的許多底圖以 XYZ/MapServer tile/{z}/{y}/{x} 形式提供,接入簡單。

?? 合規與用量:請遵守 Esri 使用條款與歸屬聲明(Attribution)。部分服務或高并發訪問可能需要 ArcGIS API Key/Token


三、項目初始化

1)創建工程

# TypeScript 推薦
npm create vite@latest ol-esri-demo -- --template vue-ts
cd ol-esri-demo
npm i

2)安裝依賴

npm i ol element-plus # element-plus 可選,用于演示切換控件

如果你使用 TailwindCSS 或 UnoCSS 也可以按需集成,這里不強依賴。


四、Esri 底圖服務速查(常用)

Esri 多數底圖可通過以下 URL 模板訪問:

https://server.arcgisonline.com/ArcGIS/rest/services/{ServicePath}/MapServer/tile/{z}/{y}/{x}

常用 ServicePath 示例(可按需取舍):

類別名稱(鍵)ServicePath說明
影像World_ImageryWorld_Imagery全球衛星/航空影像
街道World_Street_MapWorld_Street_Map全球街道底圖
地形World_Terrain_BaseWorld_Terrain_Base地形底圖(可配合注記)
物理World_Physical_MapWorld_Physical_Map物理地貌底圖
地形注記World_Terrain_ReferenceWorld_Terrain_Reference地形注記覆蓋層(Reference)
海洋底圖Ocean_BaseOcean/World_Ocean_Base海洋背景底圖
海洋注記Ocean_ReferenceOcean/World_Ocean_Reference海圖注記覆蓋層
淺灰底圖Canvas_Light_Gray_BaseCanvas/World_Light_Gray_Base灰白簡約底圖
淺灰注記Canvas_Light_Gray_ReferenceCanvas/World_Light_Gray_Reference對應注記覆蓋層
深灰底圖Canvas_Dark_Gray_BaseCanvas/World_Dark_Gray_Base深灰暗色底圖
深灰注記Canvas_Dark_Gray_ReferenceCanvas/World_Dark_Gray_Reference對應注記覆蓋層
地形陰影World_Shaded_ReliefWorld_Shaded_Relief陰影地形,常用于底紋
國界地名Boundaries_PlacesReference/World_Boundaries_and_Places國界與地名注記

🔎 提示:服務路徑可能會調整,若某個服務 404/空白,請替換為上表中其它常用項或在 ArcGIS 官方檢索同名服務。


五、最小可運行示例(Composition API)

下面是最簡實現:一個底圖源 + 一個注記源(可選),并支持按鈕切換底圖。

<!-- src/components/EsriMap.vue -->
<template><div class="container"><div class="toolbar"><el-button size="small" type="primary" @click="setBase('World_Imagery')">影像</el-button><el-button size="small" type="primary" @click="setBase('World_Street_Map')">街道</el-button><el-button size="small" type="primary" @click="setBase('World_Terrain_Base')">地形</el-button><el-button size="small" type="primary" @click="setBase('World_Physical_Map')">物理</el-button><el-switch v-model="showLabels" active-text="疊加注記" class="ml-3" /></div><div id="ol-container" /></div>
</template><script setup lang="ts">
import 'ol/ol.css'
import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
import { Map, View } from 'ol'
import TileLayer from 'ol/layer/Tile'
import XYZ from 'ol/source/XYZ'
import { fromLonLat } from 'ol/proj'// --- 工具:拼接 Esri XYZ URL ---
const esriUrl = (servicePath: string, token?: string) => {const base = `https://server.arcgisonline.com/ArcGIS/rest/services/${servicePath}/MapServer/tile/{z}/{y}/{x}`return token ? `${base}?token=${token}` : base
}// --- 常用底圖 & 注記(可按需擴充) ---
const BASEMAPS: Record<string, string> = {World_Imagery: 'World_Imagery',World_Street_Map: 'World_Street_Map',World_Terrain_Base: 'World_Terrain_Base',World_Physical_Map: 'World_Physical_Map',Canvas_Light_Gray_Base: 'Canvas/World_Light_Gray_Base',Canvas_Dark_Gray_Base: 'Canvas/World_Dark_Gray_Base',Ocean_Base: 'Ocean/World_Ocean_Base',World_Shaded_Relief: 'World_Shaded_Relief',
}const LABELS: Record<string, string> = {Boundaries_Places: 'Reference/World_Boundaries_and_Places',World_Terrain_Reference: 'World_Terrain_Reference',Canvas_Light_Gray_Reference: 'Canvas/World_Light_Gray_Reference',Canvas_Dark_Gray_Reference: 'Canvas/World_Dark_Gray_Reference',Ocean_Reference: 'Ocean/World_Ocean_Reference',
}// --- 地圖實例與圖層 ---
const map = ref<Map | null>(null)
const baseSource = new XYZ({ crossOrigin: 'anonymous' })
const labelSource = new XYZ({ crossOrigin: 'anonymous' })const baseLayer = new TileLayer({ source: baseSource })
const labelLayer = new TileLayer({ source: labelSource, visible: false })// 可選:若有 Token,可在此統一配置
const ESRI_TOKEN = '' // 例如:import.meta.env.VITE_ESRI_TOKENconst setBase = (key: keyof typeof BASEMAPS) => {baseSource.setUrl(esriUrl(BASEMAPS[key], ESRI_TOKEN))
}const setLabel = (key: keyof typeof LABELS) => {labelSource.setUrl(esriUrl(LABELS[key], ESRI_TOKEN))
}const showLabels = ref(false)watch(showLabels, (val) => {labelLayer.setVisible(val)if (val && !labelSource.getUrls() && !labelSource.getUrl()) {// 默認選擇一個通用注記setLabel('Boundaries_Places')}
})onMounted(() => {map.value = new Map({target: 'ol-container',layers: [baseLayer, labelLayer],view: new View({projection: 'EPSG:3857',center: fromLonLat([116.3913, 39.9075]), // 北京天安門示例zoom: 4,}),})// 默認加載影像底圖 + 關閉注記setBase('World_Imagery')labelLayer.setVisible(false)
})onBeforeUnmount(() => {map.value?.setTarget(undefined)map.value = null
})
</script><style scoped>
.container { width: 100%; max-width: 980px; height: 600px; margin: 24px auto; border: 1px solid #e5e7eb; border-radius: 12px; overflow: hidden; }
.toolbar { display: flex; align-items: center; gap: 8px; padding: 10px; border-bottom: 1px solid #f1f5f9; }
#ol-container { width: 100%; height: calc(600px - 50px); }
</style>

以上示例已涵蓋:

  • 動態切換不同 底圖

  • 可選疊加 注記

  • Composition API 生命周期與資源釋放;

  • token 統一拼接擴展位。


六、基于下拉選擇的優雅切換(Element Plus)

<!-- 片段:替換按鈕為下拉選擇 -->
<template><div class="toolbar"><el-select v-model="baseKey" placeholder="選擇底圖" size="small" style="width: 220px"><el-option v-for="(path, key) in BASEMAPS" :key="key" :label="key" :value="key" /></el-select><el-select v-model="labelKey" placeholder="選擇注記" size="small" style="width: 240px" :disabled="!showLabels"><el-option v-for="(path, key) in LABELS" :key="key" :label="key" :value="key" /></el-select><el-switch v-model="showLabels" active-text="疊加注記" class="ml-3" /></div>
</template><script setup lang="ts">
const baseKey = ref<keyof typeof BASEMAPS>('World_Imagery')
const labelKey = ref<keyof typeof LABELS>('Boundaries_Places')watch(baseKey, (k) => setBase(k))
watch(labelKey, (k) => { if (showLabels.value) setLabel(k) })onMounted(() => {setBase(baseKey.value)setLabel(labelKey.value)
})
</script>

七、進階:高分屏渲染與平滑體驗

OpenLayers 的 XYZ 支持以下優化參數:

const baseSource = new XYZ({crossOrigin: 'anonymous',// 高分屏:按需提高像素比(會增加帶寬)tilePixelRatio: window.devicePixelRatio > 1 ? 2 : 1,// 關閉淡入動畫,切換更干脆transition: 0,
})

提示:高像素比會明顯提升清晰度,但也會提升瓦片請求量。根據終端與網絡狀況權衡開啟。


八、為 Esri 服務添加 Attribution(歸屬)

在很多情況下你需要為底圖添加歸屬信息:

const attribution = '? Esri — Source: Esri, others. See Esri Terms.'
const baseSource = new XYZ({crossOrigin: 'anonymous',attributions: attribution,
})

務必遵守 Esri 的使用條款,不同底圖可能要求的歸屬文本略有差異,請以官方說明為準。


九、帶 Token 的安全訪問(可選)

若你的組織開啟了受保護的服務,可通過以下方式統一附加 token

const ESRI_TOKEN = import.meta.env.VITE_ESRI_TOKEN
const withToken = (url: string) => ESRI_TOKEN ? `${url}?token=${ESRI_TOKEN}` : urlconst baseSource = new XYZ({crossOrigin: 'anonymous',tileLoadFunction: (imageTile, src) => {(imageTile.getImage() as HTMLImageElement).src = withToken(src)},
})

也可以在 URL 拼接時直接加上 ?token=...,但 tileLoadFunction 更靈活,便于集中控制與替換。


十、常見問題(踩坑實錄)

  1. 首次進入空白 / 404

    • 檢查 ServicePath 是否準確;

    • 更換為本文表格中的其它服務進行對比;

    • 檢查是否需要 Token,或當前 IP/地區可用性。

  2. 跨域報錯

    • XYZ 加上 crossOrigin: 'anonymous'

    • 確保部署站點支持 HTTPS(多數 Esri 服務為 HTTPS 資源)。

  3. 坐標/投影錯亂

    • Esri 絕大多數底圖是 EPSG:3857 Web Mercator;

    • 確保 Viewprojection 與之匹配。

  4. 切換卡頓、過渡生硬

    • 設置 transition: 0 讓切換更干脆;

    • 合理選擇 tilePixelRatio

    • 不要頻繁在短時間內切換,給到請求與緩存時間。

  5. 注記不對位

    • 確保注記層與底圖同一投影(通常都是 3857);

    • 海洋、灰底等注記請使用對應的 Reference 圖層。

  6. 國內訪問偶發慢

    • 可在邊緣節點加緩存(CDN 反代);

    • 對影像類底圖設置合適的初始 zoom,避免一次性請求大量瓦片。


十一、可復用的 Basemap 注冊中心(推薦封裝)

抽離一份 esri-basemaps.ts,集中管理底圖與注記:

// src/utils/esri-basemaps.ts
export const BASEMAPS = {World_Imagery: 'World_Imagery',World_Street_Map: 'World_Street_Map',World_Terrain_Base: 'World_Terrain_Base',World_Physical_Map: 'World_Physical_Map',Canvas_Light_Gray_Base: 'Canvas/World_Light_Gray_Base',Canvas_Dark_Gray_Base: 'Canvas/World_Dark_Gray_Base',Ocean_Base: 'Ocean/World_Ocean_Base',World_Shaded_Relief: 'World_Shaded_Relief',
} as constexport const LABELS = {Boundaries_Places: 'Reference/World_Boundaries_and_Places',World_Terrain_Reference: 'World_Terrain_Reference',Canvas_Light_Gray_Reference: 'Canvas/World_Light_Gray_Reference',Canvas_Dark_Gray_Reference: 'Canvas/World_Dark_Gray_Reference',Ocean_Reference: 'Ocean/World_Ocean_Reference',
} as constexport const esriUrl = (servicePath: string) =>`https://server.arcgisonline.com/ArcGIS/rest/services/${servicePath}/MapServer/tile/{z}/{y}/{x}`

然后在組件中直接引用:

import { BASEMAPS, LABELS, esriUrl } from '@/utils/esri-basemaps'

十二、完整頁面示例(帶布局樣式)

<!--
* @Author: 彭麒
* @Date: 2025/09/01
* @Email: 1062470959@qq.com
* @Description: Vue3 + OpenLayers 加載Esri地圖(多種形式) Composition API寫法
-->
<template><div class="container"><div class="w-full flex justify-center flex-wrap"><div class="font-bold text-[24px]">在Vue3中使用OpenLayers加載Esri地圖(多種形式)</div></div><h4><el-button type="primary" size="small" @click="showmap('World_Imagery')">World_Imagery</el-button><el-button type="primary" size="small" @click="showmap('World_Street_Map')">World_Street</el-button><el-button type="primary" size="small" @click="showmap('World_Terrain_Base')">World_Terrain</el-button><el-button type="primary" size="small" @click="showmap('World_Physical_Map')">World_Physical</el-button></h4><div id="vue-openlayers"></div></div>
</template><script setup>
import 'ol/ol.css'
import { ref, onMounted } from 'vue'
import { Map, View } from 'ol'
import Tile from 'ol/layer/Tile'
import XYZ from 'ol/source/XYZ'
import { fromLonLat } from 'ol/proj'const map = ref(null)const source = new XYZ({crossOrigin: 'anonymous'
})const showmap = (x) => {source.setUrl(`https://server.arcgisonline.com/ArcGIS/rest/services/${x}/MapServer/tile/{z}/{y}/{x}`)
}const initMap = () => {map.value = new Map({target: 'vue-openlayers',layers: [new Tile({source: source}),new Tile({source: new XYZ({crossOrigin: 'anonymous',url: 'https://server.arcgisonline.com/ArcGIS/rest/services/Ocean/World_Ocean_Reference/MapServer/tile/{z}/{y}/{x}'})})],view: new View({projection: 'EPSG:3857',center: fromLonLat([-114.064839, 22.548857]),zoom: 3})})
}onMounted(() => {initMap()showmap('Ocean/World_Ocean_Base')
})
</script><style scoped>
.container {width: 840px;height: 600px;margin: 50px auto;border: 1px solid #42b983;
}
#vue-openlayers {width: 800px;height: 430px;margin: 0 auto;border: 1px solid #42b983;position: relative;
}
</style>

十三、部署與上線注意事項

  1. HTTPS:生產環境務必啟用 HTTPS,避免混合內容問題;

  2. 緩存:對靜態資源與地圖瓦片配置合理的 CDN 緩存策略;

  3. 歸屬聲明:在頁面底部或地圖角落放置 Esri 歸屬信息;

  4. 請求上限:關注訪問量與并發數,如有大量流量,考慮注冊 ArcGIS 正式 Key 并評估額度;

  5. 可用性監控:在瓦片加載失敗時上報或降級到備選底圖。


十四、小結

本文從 項目初始化Esri 服務速查最小可運行示例下拉切換、注記疊加、高分屏優化、Token 鑒權、常見問題 做了完整演示。把 ServicePath 抽到配置文件、把 Token 與 Attribution 做成統一能力,就能在實際項目中快速復用、穩定迭代。

覺得有用的話,歡迎收藏、點贊、轉發給你的同事與朋友。也歡迎在評論區補充你常用的 Esri 服務路徑與優化經驗。


附:快速檢查清單(發布前自測)

  • 不同底圖切換正常、無 404 ;

  • 注記層與底圖的投影/對齊正常;

  • 高分屏下瓦片清晰;

  • 退出頁面后地圖正確銷毀;

  • 歸屬聲明與使用條款合規;

  • 若有 Token,過期與錯誤時有兜底提示。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/97629.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/97629.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/97629.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

深入了解Flink核心:Slot資源管理機制

TaskExecutor、Task 和 Slot 簡單來說&#xff0c;它們的關系可以比作&#xff1a;TaskExecutor&#xff1a;一個工廠&#xff0c;擁有固定的生產資源。TaskSlot&#xff1a;工廠里的一個工位。每個工位都預先分配了一份獨立的資源&#xff08;主要是內存&#xff09;。Task&am…

java web 練習demo。生成簡單驗證碼前端是jsp

目錄結構 demo\ ├── WEB-INF\ │ └── weblogic.xml # WebLogic服務器配置文件 ├── demo.iml # IntelliJ IDEA項目配置文件 ├── lib\ # Java EE核心依賴庫 │ ├── javax.annotation.jar │ ├── javax.ejb.jar │ ├── javax.…

擁抱智能高效翻譯 ——8 款視頻翻譯工具深度測評

前陣子幫知識博主做跨境視頻翻譯&#xff0c;踩了不少坑&#xff1a;把 “內卷” 直譯成 “involution” 讓海外觀眾困惑&#xff0c;多語種版本趕工 3 天只出 2 種&#xff0c;還得手動核對 “碳中和”“非遺” 這類特色詞的譯法&#xff1b;用傳統工具譯完&#xff0c;視頻要…

[知識點記錄]SQLite 數據庫和MySQL 數據庫有什么區別?

核心區別&#xff1a;一個“內嵌”&#xff0c;一個“獨立”SQLite (你的個人筆記本)本質&#xff1a; 它是“無服務器”的&#xff0c;或者叫“內嵌式”數據庫。它不需要一個獨立的程序一直在后臺運行。你的應用程序&#xff08;比如Strapi&#xff09;直接就能讀寫它的數據庫…

【Spark Core】(二)RDD編程入門

目錄1 程序入口&#xff1a;SparkContext對象2 RDD的創建2.1 本地創建2.2 讀取文件創建3 RDD算子4 常用Transform算子4.1 map算子4.2 flatMap算子4.3 reduceBykey算子4.4 mapValues算子<實例> WordCount4.5 groupBy算子4.6 filter算子4.7 distinct算子4.8 union算子4.9 j…

java IDEA run/Debug異常:“jdk1.8injava.exe“ CreateProcess error=206, 文件名或擴展名太長

&#x1f9d1; 博主簡介&#xff1a;CSDN博客專家、CSDN平臺優質創作者&#xff0c;高級開發工程師&#xff0c;數學專業&#xff0c;10年以上C/C, C#,Java等多種編程語言開發經驗&#xff0c;擁有高級工程師證書&#xff1b;擅長C/C、C#等開發語言&#xff0c;熟悉Java常用開發…

Java 函數編程之【過濾器filter()合并】【predicate(斷言)】與【謂詞邏輯】

Java函數式編程之【過濾器filter合并】【predicate&#xff08;斷言&#xff09;】與【謂詞邏輯】一、合并多個過濾器filter &#xff08;Lambda版本&#xff09;二、合并多個過濾器filter &#xff08;謂詞邏輯&#xff08;Predicate&#xff09;版本&#xff09;&#xff08;…

CentOS10安裝RabbitMQ

1.下載資源 &#xff08;1&#xff09;下載erlang-rpm 注意&#xff1a;按照圖片中的下載&#xff0c;用綠色三角形指向的是重點關注的。 網址&#xff1a; erlang-rpmhttps://github.com/rabbitmq/erlang-rpm/releases &#xff08;2&#xff09;下載rabbitmq-server 注…

JVM——八股文

1. JDK, JRE和JVM的關系JDK JRE Java開發工具JRE JVM Java核心類庫JDK供Java程序開發人員開發軟件&#xff0c;JRE供客戶使用&#xff0c;只需要JVM運行環境即可。JVM運行的是class字節碼&#xff0c;不僅能運行Java代碼&#xff0c;還能運行其他語言&#xff0c;只要語言能…

騎行把帶定期換,維樂 Skin Wrap 把帶煥新騎行

在公路騎行的裝備體系里&#xff0c;把帶是最易被忽視卻至關重要的“消耗品”。它是騎手手部與車身的直接連接&#xff0c;每一次轉向、變速、剎車&#xff0c;都需通過把帶傳遞力量與操控意圖&#xff1b;同時&#xff0c;它還承擔著吸汗、減震、保護車把的作用。可長期使用后…

LeetCode100-73矩陣置零

本文基于各個大佬的文章 上點關注下點贊&#xff0c;明天一定更燦爛&#xff01; 前言 Python基礎好像會了又好像沒會&#xff0c;所有我直接開始刷leetcode一邊抄樣例代碼一邊學習吧。本系列文章用來記錄學習中的思考&#xff0c;寫給自己看的&#xff0c;也歡迎大家在評論區指…

寧波市第八屆網絡安全大賽 -- Crypto -- WriteUp

寧波市第八屆網絡安全大賽 – Crypto – WriteUp Three-prime RSA task import gmpy2 from Crypto.Util.number import *from secret import flagp getPrime(512) q getPrime(512) r getPrime(512) n p * q * r random_num getPrime(28) D ((p q r) * random_num) % n …

大語言模型 (LLM) 與多模態大模型 (MLM)

文章目錄概述&#xff1a;從“模型”到“大”模型1、大語言模型 (Large Language Model, LLM)1.1 定義與概述關鍵特征&#xff1a;1.2 核心技術與架構Transformer架構自注意力機制 (Self-Attention)1.3 訓練過程1.4 工作原理2. 多模態大模型 (Multimodal Large Model, MLM)2.1 …

HTML應用指南:利用GET請求獲取全國招商銀行網點位置信息

招商銀行&#xff08;China Merchants Bank, CMB&#xff09;作為中國領先的股份制商業銀行&#xff0c;始終堅持“以客戶為中心”的服務理念&#xff0c;致力于為個人客戶、企業客戶及機構客戶提供專業、高效、便捷的綜合金融服務。依托“輕型銀行”戰略與“金融科技銀行”建設…

JVM性能監控工具的使用

了解JVM性能監控工具并能熟練使用&#xff0c;是Java開發者進階的必備技能。下面本文將為你介紹一些主流的JVM性能監控工具及其使用方法&#xff0c;并通過一些場景案例來分析如何應用這些工具解決實際問題。 &#x1f6e0;? JVM性能監控與調優工具指南 ? 工具概覽 以下是幾款…

【工作】一些找工作需要了解避雷的知識

面試前 1.公司的具體情況 公司全稱&#xff0c;辦公地點&#xff0c;涉及崗位 要求hr做個簡單的公司介紹 2.崗位職責/業務方向 工作內容、公司業務 3.薪資待遇&#xff0c;構成&#xff0c;底薪&#xff0c;五險一金 問一下工資范圍 底薪 &#xff08;有責&#xff0c;無…

五、練習2:Git分支操作

練習2&#xff1a;Git分支操作 練習目標 掌握Git分支的創建、切換、合并等操作&#xff0c;理解分支在開發中的作用。 練習步驟 步驟1&#xff1a;準備基礎倉庫 # 創建練習目錄 mkdir branch-practice cd branch-practice# 初始化倉庫 git init# 創建初始文件 echo "# 分支…

【筆記】算法設計:異或空間線性基

Content1.什么是異或&#xff08;定義和性質&#xff09;2.異或空間線性基的構造方法3.異或空間線性基的應用4.算法設計例舉5.小結說明算法設計應用之前&#xff0c;首先明確異或空間線性基&#xff1a;一種數據結構。用于處理異或關系&#xff08;運算&#xff09;下的向量空間…

Filebeat采集數據與日志分析實戰

&#x1f31f;Filebeat采集數據的原理 Filebeat默認按行采集數據&#xff0c;如果數據沒有換行&#xff0c;則該條數據無法采集到 屬于有狀態服務&#xff0c;可以記錄上一次采集數據的位置點信息 修改配置文件 vim /etc/filebeat/config/03-log-to-console.yaml filebeat.inp…

Fluent Bit針對kafka心跳重連機制詳解(下)

#作者&#xff1a;程宏斌 文章目錄disconnectreconnect接上篇&#xff1a;https://blog.csdn.net/qq_40477248/article/details/150957571?spm1001.2014.3001.5501disconnect 斷開連接的情況主要是兩種: 連接或傳輸過程中有錯誤發生 超時, 比如空閑時間超時 ** * Close and …