借助 Trae 从 0 到 1 实现海外地图渲染(leaflet + OSM)

一. 背景

随着全球化业务发展,我们需要为海外用户提供地图服务。经过技术调研发现:

  1. Google 地图成本问题:Google Maps API 采用按量计费模式,随着用户量增长会产生高昂费用
  2. 国内地图国际化不足:百度、高德等国内地图服务对英文支持有限,海外覆盖不全面,英文展示效果不佳
  3. OpenStreetMap 优势
    • 完全免费的开源地图数据
    • 全球覆盖且支持多语言
    • 活跃的社区持续更新数据

基于以上考量,我们选择leaflet + OpenStreetMap技术方案:

  • leaflet 提供轻量级、高性能的地图展示能力
  • OpenStreetMap 提供免费可靠的全球地图数据
  • 两者结合既能满足功能需求,又能有效控制成本

二. 介绍

1. leaflet

leaflet 是一个开源的 JavaScript 库,用于创建交互式、移动友好的网络地图。它轻量级(约 39KB 的 JS 代码),但功能强大,支持大多数桌面和移动平台。

2. OpenStreetMap(OSM)

OpenStreetMap(OSM) 是一个由用户社区创建和维护的免费可编辑的世界地图,常被称为"地理维基百科"。

说到 OpenStreetMap,就不得不说一下地图瓦片。

3. 地图瓦片(Tiles)

地图瓦片是将地图分割成小的正方形图片(通常是 256×256 或 512×512 像素),按不同缩放级别组织起来的系统。这种技术允许:

  1. 高效加载:只加载当前视图需要的部分地图
  2. 快速渲染:预渲染的图片比动态渲染快
  3. 缓存友好:瓦片可以被浏览器和 CDN 缓存
  4. 并行加载:可以同时加载多个瓦片

瓦片坐标系统

瓦片通常使用(x,y,z)坐标标识:

  • z:缩放级别(0 是最小缩放,整个世界显示在一个瓦片中)
  • x 和 y:在该缩放级别下的行列位置

瓦片使用流程

  1. 请求瓦片:当用户查看地图时,Leaflet 根据当前视图的中心和缩放级别计算需要的瓦片坐标
  2. 获取瓦片:从瓦片服务器(如 OpenStreetMap 的服务器)请求这些瓦片
  3. 拼接显示:将获取的瓦片拼接成无缝的地图视图
  4. 预加载:通常还会预加载周边瓦片,以便用户平移地图时流畅显示

4. 二者关系

leaflet 是一个地图显示库,而 OpenStreetMap 是地图数据源之一,也就是瓦片源。Leaflet 可以显示来自 OpenStreetMap 的地图瓦片,也可以显示其他来源的地图数据。OpenStreetMap 数据可以被多种客户端(包括 Leaflet)使用来渲染地图。

除了 OpenStreetMap,Leaflet 的灵活性使其能够兼容多种瓦片源,包括:

  • 商业地图(Google、Bing、HERE)
  • 卫星影像(NASA、Mapbox)
  • 国家专用地图(天地图、Swisstopo)
  • 矢量瓦片(GeoServer、Mapbox)
  • 开源地图(Stamen、Esri)

具体接入方式需参考各服务的 API 文档或对应 leaflet 插件说明,本篇文章不做过多赘述!

接下来,我们将介绍如何借助 Tare 来实现 leaflet + OpenStreetMap 地图,来从 0 到 1 构建一个简单的地图应用程序。

三. 安装

接下来,我借助 Trae IDE 提供的内置智能体 Builder ,来帮助我从 0 到 1 开发一个完整的项目。根据我发出的需求, Builder 会调用不同的工具,包括运行命令的工具,来有效地处理我的需求。

在这里,我取消了 Builder 智能体的执行命令,我手动安装必要库 leaflet,最新版本为 1.9.4

sh 复制代码
pnpm install leaflet

还需要添加 types 支持,不然 ts 项目会报类型文件错误

sh 复制代码
pnpm install @types/leaflet

四. 初始化地图

安装完 leaflet 之后,我们就可以,创建一个地图组件,以 London, UK 为中心,缩放级别为 13

js 复制代码
<script lang="ts" setup>
import { onMounted } from 'vue';

import L from 'leaflet';

import 'leaflet/dist/leaflet.css';

onMounted(() => {
  const map = L.map('map').setView([51.505, -0.09], 5);

  L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    attribution:
      '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
    maxZoom: 19,
  }).addTo(map);

  // 添加标记点位
  L.marker([51.5, -0.09]).addTo(map).bindPopup('London, UK').openPopup();
});
</script>

<template>
  <div id="map" style="width: 100%; height: 500px"></div>
</template>

不同级别下的点位

这样,一个大体的雏形就出来了。

五. 暗黑模式支持

已有项目中支持暗黑模式,所以地图也要支持暗黑模式,问一下 Trae,如何实现暗黑模式?

html 复制代码
<script lang="ts" setup>
  import { onMounted, ref } from 'vue'

  import { Page } from '@vben/common-ui'

  import L from 'leaflet'

  import 'leaflet/dist/leaflet.css'

  const darkMode = ref(true)

  onMounted(() => {
    const map = L.map('map').setView([51.505, -0.09], 13)

    // 浅色图层
    const lightLayer = L.tileLayer(
      'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
      {
        attribution:
          '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
        maxZoom: 19
      }
    )

    // 深色图层
    const darkLayer = L.tileLayer(
      'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png',
      {
        attribution:
          '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
        maxZoom: 19
      }
    )

    // 根据darkMode适配主题
    darkMode.value ? darkLayer.addTo(map) : lightLayer.addTo(map)

    // 添加标记
    L.marker([51.5, -0.09]).addTo(map).bindPopup('London, UK').openPopup()
  })
</script>

<template>
  <div id="map" style="width: 100%; height: 100%"></div>
</template>

六. 优化报错问题

偶发性报错,因为 leaflet 地图容器被重复初始化导致的。我们需要优化一下流程,确保地图只在组件挂载时初始化一次,并在组件卸载时正确销毁地图实例。

通过添加销毁逻辑和使用 ref 来跟踪地图实例状态来解决这个问题。

html 复制代码
<script lang="ts" setup>
  import { onMounted, onUnmounted, ref } from 'vue'

  import { usePreferences } from '@vben/preferences'

  import L from 'leaflet'

  import 'leaflet/dist/leaflet.css'

  const { isDark } = usePreferences()
  const darkMode = ref(isDark.value)
  const mapInstance = ref<L.Map | null>(null)

  onMounted(() => {
    if (mapInstance.value) {
      mapInstance.value.remove()
    }
    mapInstance.value = L.map('map').setView([51.505, -0.09], 5)

    // 浅色图层
    const lightLayer = L.tileLayer(
      'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
      {
        attribution:
          '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
        maxZoom: 19
      }
    )

    // 深色图层
    const darkLayer = L.tileLayer(
      'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png',
      {
        attribution:
          '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
        maxZoom: 19
      }
    )

    darkMode.value
      ? darkLayer.addTo(mapInstance.value as L.Map)
      : lightLayer.addTo(mapInstance.value as L.Map)

    // 添加标记
    L.marker([51.5, -0.09])
      .addTo(mapInstance.value as L.Map)
      .bindPopup('London, UK')
      .openPopup()
  })

  onUnmounted(() => {
    if (mapInstance.value) {
      mapInstance.value.remove()
      mapInstance.value = null
    }
  })
</script>

<template>
  <div id="map" style="width: 100%; height: 100%"></div>
</template>

非常简单,以上代码,简洁版的地图组件就完成了。接下来要实现更多的功能完善,比如:添加标记组、连线、轨迹等功能。

注意:要使用 🪜 才能正确渲染地图哦!

七. 总结

leaflet + OpenStreetMap 是一个强大的组合,它提供了一个简单、灵活且高效的方式来展示全球地图。通过使用 leaflet 提供的地图显示功能,我们可以轻松地将 OpenStreetMap 的地图数据集成到我们的应用程序中。

而 OpenStreetMap 是免费的,同时覆盖了全球的大部分地区,包括大陆、海洋、南极洲等,还支持多种语言、暗黑模式等。

总之,瓦片技术是现代网络地图高效运行的基础,Leaflet 和 OpenStreetMap 的结合提供了一个强大而免费的地图解决方案。

相关推荐
李是啥也不会1 分钟前
Vue中Axios实战指南:高效网络请求的艺术
前端·javascript·vue.js
xiaoliang6 分钟前
《DNS优化真经》
前端
一只小海獭9 分钟前
了解uno.config.ts文件的配置项---转化器
前端
贾公子12 分钟前
MySQL数据库基础 === 约束
前端·javascript
代码不行的搬运工12 分钟前
HTML快速入门-4:HTML <meta> 标签属性详解
java·前端·html
Chrome深度玩家19 分钟前
如何下载Google Chrome适用于AI语音交互的特制版
前端·人工智能·chrome
JavaDog程序狗24 分钟前
【实操】uniapp纯前端搞个识别植物花草小程序
前端·vue.js·uni-app
贾公子25 分钟前
element ui & plus 版本 日期时间选择器的差异
前端·javascript
贾公子30 分钟前
form组件的封装(element ui ) 简单版本
前端·javascript
贾公子30 分钟前
下拉框组件的封装(element ui )
前端·javascript