作为一个热爱旅行的开发者,我一直想要一个能够记录和展示自己旅游足迹的功能。市面上虽然有很多地图应用,但大多功能复杂,而我只需要一个简单直观的方式来标记去过的地方和想去的地方。

于是我决定在自己的个人网站上实现一个旅游足迹地图功能。这个功能的核心需求很简单:

  • 在地图上标记去过的地方(绿色标记)
  • 标记想去的地方(橙色标记)
  • 支持点击查看详细信息
  • 适配网站的亮色/暗色主题
  • 在移动端也能良好展示

经过技术调研,我选择了 OpenLayers + 高德地图瓦片源的方案,既能满足功能需求,又能保证在国内的访问速度。

演示

旅游足迹地图演示

我的主页:https://fastcar.fun

你可以在我的关于页面看到这个旅游足迹地图的实际效果,地图上标记了我去过的城市和想去的地方。
地图演示

功能点详解

技术选型考虑

在实现这个功能时,我面临几个技术选型的问题:

地图库选择:

  • Google Maps API:需要翻墙,在国内访问不稳定
  • 百度地图 API:需要申请 key,有使用限制
  • 高德地图 API:同样需要申请 key
  • OpenLayers + 开放瓦片源:免费、灵活、无需 key

最终选择 OpenLayers 是因为它是一个功能强大的开源地图库,支持多种瓦片源,而且可以直接使用高德地图的公开瓦片服务,无需申请 API key。

瓦片源选择:
高德地图提供了公开的瓦片服务,支持多种样式:

  • 标准地图:style=8(亮色主题)
  • 暗色地图:style=7(暗色主题)
  • 卫星图:style=6
  • 路网图:style=1

这些瓦片源都支持中文标注,非常适合国内用户使用。

核心功能实现

1. 地图初始化
使用 OpenLayers 创建地图实例,配置高德瓦片源,设置合适的中心点和缩放级别。

2. 双主题支持
创建亮色和暗色两个图层,根据网站主题动态切换显示。

3. 标记系统
使用矢量图层添加标记点,区分"去过"和"想去"两种类型,使用不同的颜色和图标。

4. 交互功能
实现点击标记显示详细信息的弹窗,包括地点名称、描述、访问时间等。

5. 响应式设计
适配移动端显示,调整地图高度和弹窗样式。

架构图解

整体架构图

Astro 组件
TravelMap.astro
travel-map.json 数据
OpenLayers 地图库
高德瓦片源
矢量图层
已访问标记
愿望清单标记
主题检测系统
亮色主题图层
暗色主题图层
交互系统
点击事件处理
弹窗显示

数据流图

JSON 数据文件
组件加载
解析数据结构
创建地图实例
添加瓦片图层
创建矢量图层
遍历数据添加标记
绑定交互事件
地图渲染完成

主题切换时序图

用户站点主题系统MutationObserverTravelMap组件地图图层切换主题更新 DOM class检测到 DOM 变化触发主题检测判断当前主题隐藏亮色图层显示暗色图层显示亮色图层隐藏暗色图层alt[暗色主题][亮色主题]地图主题更新完成用户站点主题系统MutationObserverTravelMap组件地图图层

交互流程图

用户点击地图
点击位置有标记?
获取标记数据
无操作
创建弹窗内容
计算弹窗位置
显示弹窗
用户查看信息
用户点击关闭?
移除弹窗
保持显示

代码实现

地图初始化核心代码

function initMap() {// 亮色主题图层 - 高德地图标准地图lightLayer = new ol.layer.Tile({source: new ol.source.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}',attributions: '© <a href="https://www.amap.com/">高德地图</a>',maxZoom: 18})})// 暗色主题图层 - 高德地图暗色标准地图darkLayer = new ol.layer.Tile({source: new ol.source.XYZ({url: 'https://webrd0{1-4}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=7&x={x}&y={y}&z={z}',attributions: '© <a href="https://www.amap.com/">高德地图</a>',maxZoom: 18}),visible: false})// 创建地图map = new ol.Map({target: 'travel-map',layers: [lightLayer, darkLayer],view: new ol.View({center: ol.proj.fromLonLat([105.0, 35.0]), // 中国中心zoom: 4,maxZoom: 18,minZoom: 2})})
}

实现要点:

  1. 使用高德地图的公开瓦片服务,URL 中的 {1-4} 表示负载均衡的服务器
  2. style=8 是标准地图,style=7 是暗色地图
  3. lang=zh_cn 确保中文标注
  4. 初始时暗色图层设置为不可见

标记添加核心代码

function addMarkers() {const vectorSource = new ol.source.Vector()// 添加已去过的地方data.visited.forEach(location => {const feature = new ol.Feature({geometry: new ol.geom.Point(ol.proj.fromLonLat([location.coordinates[1], location.coordinates[0]])),type: 'visited',data: location})vectorSource.addFeature(feature)})// 添加想去的地方data.wishlist.forEach(location => {const feature = new ol.Feature({geometry: new ol.geom.Point(ol.proj.fromLonLat([location.coordinates[1], location.coordinates[0]])),type: 'wishlist',data: location})vectorSource.addFeature(feature)})const vectorLayer = new ol.layer.Vector({source: vectorSource,style: function(feature) {const type = feature.get('type')return new ol.style.Style({image: new ol.style.Circle({radius: 15,fill: new ol.style.Fill({color: type === 'visited' ? '#22c55e' : '#f59e0b'}),stroke: new ol.style.Stroke({color: type === 'visited' ? '#16a34a' : '#d97706',width: 3})}),text: new ol.style.Text({text: type === 'visited' ? '✓' : '♡',fill: new ol.style.Fill({ color: 'white' }),font: 'bold 16px sans-serif'})})}})map.addLayer(vectorLayer)
}

实现要点:

  1. 注意坐标转换:ol.proj.fromLonLat([经度, 纬度])
  2. 使用不同颜色区分标记类型:绿色表示已访问,橙色表示愿望清单
  3. 添加文字图标:✓ 和 ♡ 增强视觉识别

主题切换核心代码

function detectSiteTheme() {// 检测站点当前主题const isDark = document.documentElement.classList.contains('dark') ||document.body.classList.contains('dark') ||document.documentElement.getAttribute('data-theme') === 'dark'currentTheme = isDark ? 'dark' : 'light'switchTheme(currentTheme)
}function switchTheme(theme) {if (theme === 'dark') {// 暗色主题:显示高德暗色地图lightLayer.setVisible(false)darkLayer.setVisible(true)} else {// 亮色主题:显示高德标准地图lightLayer.setVisible(true)darkLayer.setVisible(false)}
}function watchSiteThemeChanges() {// 使用MutationObserver监听站点主题变化const observer = new MutationObserver(function(mutations) {mutations.forEach(function(mutation) {if (mutation.type === 'attributes' &&(mutation.attributeName === 'class' || mutation.attributeName === 'data-theme')) {detectSiteTheme()}})})observer.observe(document.documentElement, {attributes: true,attributeFilter: ['class', 'data-theme']})
}

实现要点:

  1. 支持多种主题检测方式,兼容不同的主题切换实现
  2. 使用 MutationObserver 监听 DOM 变化,实现主题自动切换
  3. 通过图层的 setVisible() 方法控制显示/隐藏

数据结构设计

{"visited": [{"id": "beijing","name": "北京","nameEn": "Beijing","coordinates": [39.9042, 116.4074],"description": "中国首都","visitDate": "2023-05"}],"wishlist": [{"id": "xian","name": "西安","nameEn": "Xi'an","coordinates": [34.3416, 108.9398],"description": "古都西安,兵马俑故乡","priority": "high"}]
}

设计要点:

  1. 分离已访问和愿望清单数据
  2. 坐标使用 [纬度, 经度] 格式
  3. 支持中英文名称
  4. 可扩展字段:访问时间、优先级等

简单总结

通过使用 OpenLayers + 高德瓦片源,我成功实现了一个功能完整的旅游足迹地图。这个方案的主要优势:

技术优势:

  • 无需申请 API key,降低使用门槛
  • 支持自定义样式和交互
  • 高德地图在国内访问速度快,中文支持好
  • OpenLayers 功能强大,扩展性好

功能特色:

  • 双主题自动切换,与网站整体风格保持一致
  • 直观的标记系统,清晰区分已访问和愿望清单
  • 响应式设计,移动端体验良好
  • 交互弹窗提供详细信息展示

后续改进方向:

  1. 添加路线规划功能,连接相邻的旅游点
  2. 支持照片上传,为每个地点添加旅游照片
  3. 增加统计功能,显示旅游里程、访问城市数量等
  4. 支持数据导入导出,方便备份和分享
  5. 添加搜索功能,快速定位特定地点
  6. 集成天气信息,显示各地实时天气

这个旅游足迹地图不仅满足了我个人记录旅游经历的需求,也为网站访客提供了一个了解我旅游足迹的有趣方式。通过开源的技术栈实现,也为其他开发者提供了一个可参考的实现方案。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/96523.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/96523.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/96523.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Redis基础(含常用命令等以快速入门)

一、初步认识 1、NoSQL SQL 关系型数据库&#xff08;表结构&#xff0c;强一致&#xff09;NoSQL 非关系型数据库&#xff08;灵活结构&#xff0c;最终一致&#xff0c;水平扩展爽&#xff09; 维度SQL&#xff08;关系型&#xff09;NoSQL&#xff08;非关系型&#xf…

OSPF特殊区域、路由汇总及其他特性

OSPF路由器需要同时维护域内路由、域间路由、外部路由信息数据库。当网络规模不断扩大时&#xff0c;LSDB规模也不断增长。如果某区域不需要为其他区域提供流量中转服务&#xff0c;那么该区域内的路由器就没有必要维护本区域外的链路状态数据库。OSPF通过划分区域可以减少网络…

在缓存Cacheable注解中Key值如何使用常量

1.在常量类中定义商品缓存空间和商品缓存KEY public interface CacheConstants {/*** Goods Cache Name*/String QNA_GOODS_CACHE "qna-goods";/*** Goods Cache key*/String QNA_GOODS_CACHE_KEY "qna_goods:";/*** Order Cache Name*/String QNA_ORDER…

sklearn聚类

在此将sklearn官网的一张关于聚类算法比较的图片放过来。 下面的表格是根据sklearn官网翻译而来。 方法名称 参数 可扩展性 应用场景 几何度量(距离) MiniBatchKMeans 簇的数量 非常适合处理大量样本和中等数量的簇(使用MiniBatch时) 通用型,适用于簇大小均匀、几何形状平…

Recharts:React图表库,组件化设计助力高效数据可视化开发

你写前端项目时有没有卡过数据可视化的坑&#xff1f;比如要做个用户增长折线图&#xff0c;查了半天原生 JS 教程&#xff0c;写了几十行代码&#xff0c;结果要么坐标轴对不上&#xff0c;要么数据渲染不出来&#xff1b;或者用了某个图表库&#xff0c;文档全是英文&#xf…

Java 中String类的常用方法

Java 中的 String 类提供了丰富的方法用于字符串操作&#xff0c;以下是最常用的一些方法分类总结&#xff1a; 一、获取字符串信息length()&#xff1a;返回字符串长度&#xff08;字符个数&#xff09; String s "hello"; int len s.length(); // len 5charAt(i…

【记录】Docker|Docker内部访问LInux主机上的Ollama服务

部分内容参考自&#xff1a;使得 docker 容器内部可以访问宿主机的 ollama 服务_docker 访问 ollama-CSDN 博客&#xff0c;补充添加了更多的细节&#xff0c;也补充了一个更加简单的方案。 我测试的系统版本&#xff1a;Ubuntu 24.04.2 LTS noble&#xff0c;查看方式是指令 l…

数据库物理外键与逻辑外键全解析

一、核心概念 1. 物理外键 (Physical Foreign Key) 物理外键是数据库层面通过语法明确创建的外键约束。它是由数据库管理系统&#xff08;DBMS&#xff09;本身&#xff08;如 MySQL, PostgreSQL, Oracle&#xff09;来强制实现的。 它是什么&#xff1a;数据库表结构的一部分&…

Vue3入门到实战,最新版vue3+TypeScript前端开发教程,创建Vue3工程,笔记03

笔记03 一、创建Vue3项目 1.1、创建方式 使用vue-cli创建使用vite创建&#xff08;推荐&#xff09;Vue3官网创建项目文档 两种创建方式&#xff0c;推荐使用第二种。vue-cli是基于webpack实现的&#xff0c;vite是新一代前端构建工具。 2.1、vue3项目结构

企业如何利用群晖 NAS 构建高效数据备份与容灾体系

在数字化转型的过程中&#xff0c;企业数据已成为核心资产。然而&#xff0c;勒索病毒攻击、硬件故障、操作失误以及自然灾害等风险&#xff0c;都可能导致数据丢失甚至业务中断。如何构建一个高效、安全、可恢复的数据备份与容灾体系&#xff0c;已成为企业 IT 管理的关键课题…

关于在pycharm终端连接服务器

1、先为每个项目创建一个虚拟环境2、使用命令下载torchpip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple安装之后发现安装在了本地&#xff0c;我需要安装到服务器里面&#xff0c;此时可以把本地的删除&#xff0c;因为是默认安装到c盘&#xff0c;除非你指定路…

CSS 继承 (Inheritance)

一、核心概念CSS 继承是指某些 CSS 属性如果被设置在父元素上&#xff0c;其值会自动流向&#xff08;应用到&#xff09;其所有后代元素&#xff08;子、孙元素等&#xff09;的特性。核心价值&#xff1a;通过将样式声明应用于祖先元素&#xff0c;可以避免在所有后代元素上重…

UGUI源码剖析(15):Slider的运行时逻辑与编辑器实现

UGUI源码剖析&#xff08;第十五章&#xff09;&#xff1a;Slider的运行时逻辑与编辑器实现 在之前的章节中&#xff0c;我们已经深入了UGUI众多核心组件的运行时源码。然而&#xff0c;一个完整的Unity组件&#xff0c;通常由两部分构成&#xff1a;定义其在游戏世界中行为的…

【Python】爬虫html提取内容基础,bs4

前言 BeautifulSoup也就是bs4,里面功能其实有很多&#xff0c;不过对于爬虫而言主要掌握一下几块就可以了 怎么找标签&#xff1f;找到标签后怎么获取属性&#xff0c;怎么获取文本内容如何通过找到的标签继续获取子标签 安装 pip install bs4案例 对于找标签来说&#xf…

组件库打包工具选型(npm/pnpm/yarn)的区别和技术考量

组件库打包工具选型&#xff1a;npm/pnpm/yarn的区别与技术考量 一、核心差异概述 组件库打包工具的选择&#xff0c;本质是在​​依赖管理效率​​、​​磁盘空间占用​​、​​Monorepo支持​​、​​安装速度​​及​​幽灵依赖风险​​之间做权衡。npm作为Node.js默认工具…

新型APT组织“嘈杂熊“针对哈萨克斯坦能源部门发起网络间谍活动

感染链图示 | 图片来源&#xff1a;Seqrite实验室APT研究团队 Seqrite实验室APT研究团队近日发布了一份深度分析报告&#xff0c;披露了一个自2025年4月起活跃的新型威胁组织"嘈杂熊"(Noisy Bear)。该组织主要针对哈萨克斯坦石油天然气行业&#xff0c;攻击手法结合…

OpenCV 图像直方图

目录 一、什么是图像直方图&#xff1f; 关键概念&#xff1a;BINS&#xff08;区间&#xff09; 二、直方图的核心作用 三、OpenCV 计算直方图&#xff1a;calcHist 函数详解 1. 函数语法与参数解析 2. 基础实战&#xff1a;计算灰度图直方图 代码实现 结果分析 3. 进…

Firefox Window 开发流程(四)

1 引言 在进行 Firefox 浏览器的二次开发、内核研究或自定义构建之前&#xff0c;最重要的步骤就是拉取源码并进入 Mozilla 官方提供的开发引导模式。这不仅是所有定制工作的起点&#xff0c;同时也决定了后续开发环境的稳定性与可维护性。本文将从源码获取、工具使用、引导脚…

mybatis plus 使用wrapper输出SQL

在MyBatis-Plus中&#xff0c;Wrapper对象用于构建复杂的查询条件。虽然MyBatis-Plus本身没有直接提供从Wrapper对象获取完整SQL语句的方法&#xff0c;但你可以通过一些间接的方式来获取生成的SQL片段。以下是如何使用MyBatis-Plus的Wrapper来获取SQL片段的步骤&#xff1a;‌…

第1章:操作系统和计算机网络

1. 操作系统和计算机网络组成目标概述1.1. 核心知识操作系统和网络知识很庞大&#xff0c;大多内容枯燥无味&#xff0c;主功最常用的&#xff0c;符合2/8原则。操作系统&#xff1a;内核、性能、磁盘IO、内存、CPU进程、线程、文件、中断计算机网络&#xff1a;OSI七层模型、T…