开源北京地铁足迹地图:全栈开发与数据可视化实践

开源北京地铁足迹地图:全栈开发与数据可视化实践 这个项目可以说完全是从一个非常具体的痛点里长出来的我每天在北京坐地铁通勤时间久了就非常想知道自己到底刷通过多少条线路、去过多少个站。市面上的足迹记录工具要么绑定第三方账号要么导出数据很费劲要么根本不会细分到“地铁站点”这个颗粒度。后来我干脆自己写了一个北京地铁足迹地图记录工具现在已经上线代码也开源了。这次写这篇文章不是简单贴一个仓库地址而是把整个项目的定位、技术设计、部署启动、功能验证、API 扩展和踩坑记录都整理出来。如果你正好也在做类似的路线记录、打卡统计、足迹可视化工具或者只是想把一个 Web 小项目完整落地并开源那这篇文章可以直接收藏。你会看到这个工具解决了什么问题、我为什么用这套技术栈、如何在本地跑起来、数据模型怎么设计、批量导入导出怎么做、后端 API 怎么暴露给二次开发以及上线后最容易踩的坑在哪里。1. 核心能力速览先看项目整体面貌能力项说明项目定位北京地铁站点与线路足迹记录、统计和可视化工具开源情况已上线并开源仓库地址以项目 README 为准核心功能站点打卡记录、线路完成度统计、足迹地图展示、数据导出导入数据存储默认使用 SQLite 单文件数据库方便备份和迁移地图渲染Leaflet 地图默认可切 OpenStreetMap 或其他在线地图源前端技术Vue 3 Vite Leaflet组件化实现打卡面板与地图联动后端技术Python FastAPI提供 REST API支持数据读写和批量导入启动方式前后端分离开发模式也可用 Docker 一键启动API 能力站点查询、记录新增、记录删除、批量导入、数据导出批量任务支持 CSV / JSON 批量导入适合一次性同步历史足迹数据适合用户地铁通勤记录爱好者、足迹可视化开发者、全栈学习实践者需要说明的是表里这些能力是我在项目规划和实现中确定的模块。如果你是从仓库拉下来的最新代码具体端口、接口路径和默认地图源要以 README 和实际运行日志为准。2. 适用场景与使用边界这个工具的核心使用场景是“记录我去过哪些地铁站并用地图和统计页展示成果”。它不收集实时位置不监听后台 GPS只在你主动添加记录时写入数据。所以它适合这几类人第一类是地铁出行爱好者。比如你想知道北京地铁全网 27 条线、几百座车站里自己解锁了多少站哪些线路还差多少站就能“全线打通”。这类目标非常适合用足迹地图来跟踪。第二类是习惯把生活数据记录本地化的用户。这个工具的数据默认存在自己的 SQLite 文件里你可以随时导出 JSON 或 CSV不依赖服务商账号也不怕平台倒闭后数据丢失。第三类是开发者。你可以在开源基础上改造成其他城市的版本修改站点数据文件或者换掉前端地图组件接入自己熟悉的技术栈。因为接口和数据模型都比较简单二次开发成本不高。同时也要说清楚使用边界。这个工具不是实时导航也请不要拿它去做任何形式的轨迹追踪、人员定位或监控类应用。地铁线路图、站名、坐标等基础数据来源于公开资料你如果要发布自己的版本需要确认数据来源许可并在页面适当位置做版权说明。尤其是地图瓦片服务如果使用在线地图服务商提供的底图要遵守服务商的调用条款必要时申请合法的 Key。还有一点足迹数据属于个人出行信息默认存储在当前部署环境中不要把这个服务直接暴露到公网而没有任何访问限制尤其是当你导入的是真实通勤记录时。3. 项目设计与技术选型3.1 整体架构这是一个典型的前后端分离 Web 应用。前端负责地图渲染、打卡表单和统计展示后端负责数据管理和 API 暴露数据库单独一层便于替换。整个请求链路大致是浏览器中的 Vue 页面 → 调用 FastAPI 接口 → 读写 SQLite 表 → 返回 JSON 数据 → 前端解析后在地图上绘制已打卡站点。我选择前后端分离而不是纯静态页 localStorage主要考虑两点一是未来想支持多设备记录数据能集中在服务端二是批量导入、去重、统计这类逻辑放在后端更好维护也方便下次做一个小程序管理端直接复用同一套 API。3.2 数据模型设计数据模型是整个项目的地基。站点、线路、打卡记录三个维度是核心我用三张表来描述线路表 lines字段类型说明idINTEGER PK线路 IDnameTEXT线路名称如 1 号线、10 号线colorTEXT线路展示色站点表 stations字段类型说明idINTEGER PK站点 IDline_idINTEGER所属线路nameTEXT站点名称seqINTEGER在线路上的顺序latREAL纬度lngREAL经度打卡记录表 footprints字段类型说明idINTEGER PK记录 IDstation_idINTEGER对应站点visited_atTEXT打卡时间noteTEXT备注信息为什么要单独建站点表而不是在打卡记录里直接存站点名因为线路和站点是相对固定的数据单独成表后可以避免每个月台名称写成“西直门”和“西直门站”这种不一致问题。前端选择站点时直接下拉站点表后端也只接受站点 ID数据规范很多。线路、站点这类静态数据放到两张表之后我建议用 JSON 文件作为数据源来初始化数据库。比如首次启动时后端检查站点表为空就读取站点的 JSON 文件写入 SQLite。这样后续要改成上海地铁、广州地铁只需要替换数据文件而不改业务代码。3.3 地图渲染方案地图组件我用的是 Leaflet。选择它而不是直接用某个国内地图 SDK是因为 Leaflet 轻量、开源、插件生态丰富而且底图源可替换。你完全可以在配置文件里把底图换成不同的瓦片服务例如高德地图的瓦片地址或者百度地图的瓦片地址。需要注意一个关键点一旦换成商业地图服务商的瓦片就意味着你的页面可能依赖对方提供的 Key 和服务条款。个人学习项目使用问题不大正式对外发布时务必确认授权。更稳妥的做法是保留 OpenStreetMap 作为默认底图并在页面底部保留地图版权归属说明或者自己准备合规的地图服务。3.4 技术栈选择理由后端我用了 Python FastAPI因为这类工具的核心逻辑是 CRUD、批量导入和统计FastAPI 写起来非常快而且自带 OpenAPI 文档前端联调时直接打开/docs页面就能看到接口说明。如果你更熟悉 Node.js 或 Java后端完全可以替换成 Express、Spring Boot只要保持 API 路径和数据格式不变前端不需要大改。前端用 Vue 3 Vite主要是看重组件化开发和热更新效率。页面拆成四个区域顶部统计卡片、左侧站点选择与打卡面板、中间地图、右侧最近记录列表。数据持久化选择 SQLite原因也很直接个人工具不需要单独部署 MySQL一个.db文件就能搞定数据存储备份就是复制文件。等以后用户量大了再换 PostgreSQL 也不难因为数据访问都封装在仓储函数里没有散落在业务代码中。4. 本地部署与启动方式下面这套流程是完整的前后端分离启动方式。以下命令是通用模板因为每个仓库的目录结构可能会有差异实际执行时以项目 README 为准。4.1 环境准备建议环境Python 3.10 或更高版本Node.js 18 或更高版本GitDocker可选如果使用容器化启动先确认本机版本python --version node -v npm -v git --version然后克隆项目git clone 你的项目仓库地址 cd beijing-metro-footprint这里用占位符代替真实仓库地址开源地址请以项目说明页为准。4.2 启动后端进入后端目录创建虚拟环境并安装依赖cd backend python -m venv venv source venv/bin/activate # Windows 下使用venv\Scripts\activate pip install -r requirements.txt启动后端服务uvicorn main:app --host 127.0.0.1 --port 8000 --reload启动后可以访问接口文档页面确认服务正常http://127.0.0.1:8000/docs在这个页面里你应该能看到站点查询、打卡记录管理、批量导入、数据导出等接口。如果打不开先检查端口是否被占用或者看终端报错日志。4.3 启动前端打开一个新终端进入前端目录cd frontend npm install npm run dev默认开发服务地址通常是http://127.0.0.1:5173浏览器打开这个地址前端页面会尝试请求后端的 API。如果页面能显示地图、线路列表为空或站点列表为空优先检查后端是否已经启动以及前端环境文件里配置的 API 地址是否指向http://127.0.0.1:8000。4.4 Docker 启动如果仓库提供了 Dockerfile 和 docker-compose 配置更简单的方式是整体启动docker-compose up -d启动后访问前端地址和接口文档地址确认。Docker 方式的好处是自动隔离 Python 和 Node 环境不污染本机依赖缺点是首次构建镜像需要拉取依赖耗时取决于网络情况。5. 功能测试与效果验证项目跑起来之后建议按下面的顺序做一轮完整功能验证确认每一步都符合预期再开始使用。5.1 地图加载测试打开前端页面确认地图能够正常渲染。如果底图是 OpenStreetMap页面偶尔出现灰块通常是网络请求瓦片失败可以切换一个网络环境或者修改地图源配置。判断标准是地图可以拖动、缩放并且初始中心点能落在北京区域。5.2 添加打卡记录测试在站点列表中选择一个站点比如“西直门”打卡时间默认取当前时间备注填写“换乘测试”。点击提交后观察地图上是否出现该站点的标记统计卡片里的“已打卡站点数”是否变成 1右侧最近记录列表是否出现这条记录。这一步同时验证了后端写入、前端联动、统计刷新三个环节是整个项目最基本的功能闭环。5.3 删除与修改记录测试在最近记录列表中找到刚才添加的记录执行删除操作。删除后确认地图标记消失、统计数字回退。这个测试不是单纯的破坏性验证而是为了确认数据一致性站点标记是实时从数据库中查询出来的而不是前端内存里临时写死的刷新页面后状态仍然正确。5.4 线路完成度统计测试把某条线路下的所有站点依次打卡观察该线路的“已覆盖站点数 / 总站点数”和“完成进度条”是否正确。比如某条线有 20 个站你添加完 20 条记录后完成度应显示 100%统计卡片中“已刷通线路数”也会增加。如果统计不符合预期优先检查站点数据文件中该线路的站点数量是否正确以及后端统计接口里的去重逻辑是否生效。常见的错误是同一站点重复打卡时被重复统计这时候需要对station_id做去重处理。5.5 批量导入与导出测试准备一个 CSV 文件内容格式类似station_id,visited_at,note 15,2025-01-05 09:30:00,通勤 32,2025-01-06 18:20:00,下班路过在后端接口页面调用批量导入接口上传后返回成功数量。再调用导出接口确认导出的 JSON 或 CSV 里包含刚导入的记录。这一步同时验证了批量任务能力和数据备份能力是足迹记录工具里最实用的功能之一。判断成功的标准包括导入数量正确、时间字段没有被时区改乱、再导出时数据与导入前一致。如果导入时出现中文乱码注意 CSV 需要 UTF-8 编码导出时间字段建议统一为 ISO 8601 格式避免不同浏览器解析差异。6. 数据存储与批量记录设计6.1 为什么用 SQLite 单文件SQLite 对这个小工具来说是最合理的选择。你不需要安装数据库服务不需要配置用户名密码一个文件就是整个数据库。备份的时候直接复制文件迁移部署时也只需要把文件带到新环境。如下是初始化建表语句的一种写法CREATE TABLE IF NOT EXISTS lines ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE, color TEXT ); CREATE TABLE IF NOT EXISTS stations ( id INTEGER PRIMARY KEY AUTOINCREMENT, line_id INTEGER NOT NULL, name TEXT NOT NULL, seq INTEGER DEFAULT 0, lat REAL, lng REAL ); CREATE TABLE IF NOT EXISTS footprints ( id INTEGER PRIMARY KEY AUTOINCREMENT, station_id INTEGER NOT NULL, visited_at TEXT NOT NULL, note TEXT );实际项目中还可以加created_at、updated_at字段方便后续按时间段筛选数据。6.2 CSV 批量导入逻辑批量导入的重点不是“能读文件”而是“数据对不对”。我在后端做了三层校验第一层检查 CSV 表头是否存在station_id和visited_at字段。字段缺失直接返回错误避免导入一堆无效数据。第二层检查station_id是否存在于站点表。如果 CSV 里写了不存在的站点 ID跳过该行并记录原因。第三层同一条站点同一次打卡时间不允许重复导入。写入前查询是否已存在相同station_id和visited_at的记录存在则跳过。这种设计看起来多做了一步查询但对于足迹记录工具来说非常关键。因为很多人第一次使用时会拿历史数据一次性导入如果不做去重第二次导入同一份文件就会产生大量重复记录。6.3 数据导出格式导出接口会生成一个包含线路、站点、打卡记录三层结构的 JSON 文件方便二次处理。导出成功后的判断标准是每个站点能关联到线路每条打卡记录能关联到站点。这里提供一个通用导出脚本思路# 导出伪代码实际接口按项目代码为准 def export_data(db): lines db.query(SELECT * FROM lines) stations db.query(SELECT * FROM stations) footprints db.query(SELECT * FROM footprints) return { lines: lines, stations: stations, footprints: footprints, }导出 CSV 则简单得多只导出打卡记录表内容每一行对应一条记录。推荐同时提供两种格式JSON 用于完整备份CSV 用于 Excel 打开编辑。7. 后端 API 与二次开发7.1 核心 API 一览后端提供的核心接口可以划分为四组接口模块请求方法与路径功能说明线路查询GET /api/lines获取所有地铁线路站点查询GET /api/stations?line_id1按线路筛选站点新增打卡POST /api/footprints添加一条足迹记录删除打卡DELETE /api/footprints/{id}删除足迹记录足迹列表GET /api/footprints获取打卡记录列表批量导入POST /api/import上传 CSV / JSON 批量导入导出数据GET /api/export导出完整数据备份具体的参数格式和返回结构以项目代码里的 FastAPI 模型为准打开/docs页面即可看到完整说明。7.2 curl 调用示例新增打卡接口的一个通用调用方式curl -X POST http://127.0.0.1:8000/api/footprints \ -H Content-Type: application/json \ -d { station_id: 15, visited_at: 2025-01-05 09:30:00, note: 通勤 }正常返回时会带有新的记录 ID说明写入成功。查询某条线路站点curl http://127.0.0.1:8000/api/stations?line_id17.3 Python 调用示例如果你想把打卡能力接入自己的脚本比如每天下班自动打卡可以用 requests 库写一个简单脚本import requests BASE_URL http://127.0.0.1:8000 response requests.get(f{BASE_URL}/api/stations, params{line_id: 1}) if response.status_code 200: stations response.json() print(f获取站点数: {len(stations)}) # 新增打卡示例 payload { station_id: stations[0][id], visited_at: 2025-01-05 18:00:00, note: 自动测试 } resp requests.post(f{BASE_URL}/api/footprints, jsonpayload) print(resp.status_code, resp.json())7.4 二次开发方向接口设计得足够简单后二次开发的空间就打开了。你可以做一个命令行工具根据乘车记录自动打卡也可以做成微信小程序扫码进出站后自动同步数据甚至可以在周末统计接口的基础上生成一张“本赛季刷站量排行榜”。比较推荐先做的扩展有两个第一增加“打卡日期范围统计接口”。目前统计都是全量数据如果要看单月、单周的数据可以在后端增加时间筛选参数前端再做一个月度热力图。第二增加“近似站点合并逻辑”。有些线路会因为换乘站顺序不同而带来重复感知可以按站点名称去重后展示不同线路上的同一换乘站。这个逻辑放在后端做前端渲染会简单很多。8. 常见问题与排查方法上线和本地部署过程中出现频率比较高的问题基本集中在这一张表里问题现象可能原因排查方式解决方案前端页面无法访问前端服务未启动或端口被占用查看终端日志、检查端口重启前端服务或修改 dev 端口页面能开但站点列表为空后端未启动或前端 API 地址配置错误打开浏览器开发者工具看网络请求确认后端地址能访问修改前端环境变量后端接口文档打不开后端启动失败或端口冲突查看后端日志更换端口或关闭占用进程地图显示灰块瓦片服务加载失败打开浏览器控制台看瓦片请求状态更换地图源或检查网络环境批量导入失败CSV 编码或字段格式不匹配用文本编辑器检查 CSV 原始内容统一转为 UTF-8 编码补齐表头字段重复导入后数据量翻倍未做去重逻辑查看数据库记录加入按站点和时间去重的判断打卡时间不对时区设置不一致检查数据库中的时间字段统一为北京时间并保持 ISO 8601 格式中文乱码终端编码或文件编码问题用命令行查看返回字符设置PYTHONIOENCODINGutf-8或检查 CSV 编码统计完成度偏高/偏低站点表数据与公开线路图不一致核对站点 JSON 数据文件修正数据源后重新初始化站点表如果遇到后端依赖安装失败可以优先尝试升级 pip 和 setuptools再使用国内 Python 镜像源安装。如果遇到 npm 依赖安装缓慢同样可以切换 npm 镜像源。9. 最佳实践与使用建议9.1 数据目录分清楚建议把项目里的代码、站点数据、数据库备份分成三个目录。代码目录存放前端和后端源码数据目录存放站点 JSON 文件和 SQLite 数据库文件备份目录存放每次导出的 JSON/CSV。这样升级代码时不会误删数据库备份时也能快速定位文件。9.2 第一次使用先导入后手动补如果你有大量历史足迹数据不要急着手动一条条打卡。先准备 CSV 文件批量导入导入完成后用地图页面抽查几个站点验证数据准确度。之后日常使用就只记录增量不需要每天面对一张空地图。9.3 给接口加访问限制如果这个工具部署在公网千万不要把接口完全暴露。最简单的做法是在后端加一个 Token 校验所有 API 请求都要求请求头里带访问令牌更严格的做法是用反向代理加白名单限制 IP 访问范围。毕竟足迹数据是个人敏感信息不做访问控制很容易被别人批量拉取。9.4 关于人脸、声音和位置数据的合规提醒这个项目本身只涉及站点坐标和打卡时间不涉及人脸和声音。但如果你后续做二次开发加入了用户定位、出行轨迹、人脸识别或者其他个人信息处理能力一定要遵守个人信息保护相关法律法规做到最小化收集、明确告知、用户授权并为数据删除提供入口。9.5 发布和商用前复核开源不等于可以随意使用所有素材。如果你要发布一个二次修改版请注意三点地图底图的版权与服务条款站点数据的来源许可以及代码依赖中各开源库的许可证类型。如果你修改后要商业化建议先做一轮代码审查和合规确认避免商标和数据版权方面的风险。10. 总结与下一步这个项目最值得尝试的地方在于它是一个完整落地并开源的全栈应用麻雀虽小但结构完整包含地图展示、统计计算、数据导入导出、API 设计、Docker 部署等常见工程环节。你可以直接使用它来记录自己的北京地铁足迹也可以拿它作为学习前后端分离开发、Leaflet 地图集成、SQLite 数据建模的练手项目。最容易踩的坑不在代码复杂度而在数据规范。站点数据只要有一点坐标偏差或线路归类错误地图展示就会很别扭。花费时间最多的往往不是写接口而是整理北京地铁线路和站点的静态数据。下一步我准备做的方向有三个一是增加基于日历的足迹热力图可以一眼看出每个月的通勤频率二是让线路完成度统计支持“换乘站去重”和“全部换乘站算一次”两种模式满足不同统计口径三是补充一个更完整的自动化测试用例把导入、去重、统计三个高风险模块用测试固定下来方便后续继续加功能。如果你也准备试一下建议先跑通一键启动然后立刻做一次批量导入导出测试。能把数据完整地导出来这个工具的基本盘就稳了。后续再按自己的使用习惯去改前端界面和统计维度都很容易。把项目做成开源最大的收获不是代码本身而是让有同样需求的人可以少走一些弯路。