开源北京地铁足迹地图:全栈开发与数据可视化实践
2026/9/7 20:55:24 网站建设 项目流程

这个项目可以说完全是从一个非常具体的痛点里长出来的:我每天在北京坐地铁通勤,时间久了就非常想知道,自己到底刷通过多少条线路、去过多少个站。市面上的足迹记录工具要么绑定第三方账号,要么导出数据很费劲,要么根本不会细分到“地铁站点”这个颗粒度。后来我干脆自己写了一个北京地铁足迹地图记录工具,现在已经上线,代码也开源了。

这次写这篇文章,不是简单贴一个仓库地址,而是把整个项目的定位、技术设计、部署启动、功能验证、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线路 ID
nameTEXT线路名称,如 1 号线、10 号线
colorTEXT线路展示色

站点表 stations:

字段类型说明
idINTEGER PK站点 ID
line_idINTEGER所属线路
nameTEXT站点名称
seqINTEGER在线路上的顺序
latREAL纬度
lngREAL经度

打卡记录表 footprints:

字段类型说明
idINTEGER PK记录 ID
station_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 或更高版本
  • Git
  • Docker(可选,如果使用容器化启动)

先确认本机版本:

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_atupdated_at字段,方便后续按时间段筛选数据。

6.2 CSV 批量导入逻辑

批量导入的重点不是“能读文件”,而是“数据对不对”。我在后端做了三层校验:

第一层,检查 CSV 表头是否存在station_idvisited_at字段。字段缺失直接返回错误,避免导入一堆无效数据。

第二层,检查station_id是否存在于站点表。如果 CSV 里写了不存在的站点 ID,跳过该行并记录原因。

第三层,同一条站点同一次打卡时间不允许重复导入。写入前查询是否已存在相同station_idvisited_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_id=1按线路筛选站点
新增打卡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_id=1"

7.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", json=payload) print(resp.status_code, resp.json())

7.4 二次开发方向

接口设计得足够简单后,二次开发的空间就打开了。你可以做一个命令行工具,根据乘车记录自动打卡;也可以做成微信小程序,扫码进出站后自动同步数据;甚至可以在周末统计接口的基础上,生成一张“本赛季刷站量排行榜”。

比较推荐先做的扩展有两个:

第一,增加“打卡日期范围统计接口”。目前统计都是全量数据,如果要看单月、单周的数据,可以在后端增加时间筛选参数,前端再做一个月度热力图。

第二,增加“近似站点合并逻辑”。有些线路会因为换乘站顺序不同而带来重复感知,可以按站点名称去重后展示不同线路上的同一换乘站。这个逻辑放在后端做,前端渲染会简单很多。

8. 常见问题与排查方法

上线和本地部署过程中,出现频率比较高的问题基本集中在这一张表里:

问题现象可能原因排查方式解决方案
前端页面无法访问前端服务未启动或端口被占用查看终端日志、检查端口重启前端服务或修改 dev 端口
页面能开但站点列表为空后端未启动,或前端 API 地址配置错误打开浏览器开发者工具看网络请求确认后端地址能访问,修改前端环境变量
后端接口文档打不开后端启动失败或端口冲突查看后端日志更换端口或关闭占用进程
地图显示灰块瓦片服务加载失败打开浏览器控制台看瓦片请求状态更换地图源或检查网络环境
批量导入失败CSV 编码或字段格式不匹配用文本编辑器检查 CSV 原始内容统一转为 UTF-8 编码,补齐表头字段
重复导入后数据量翻倍未做去重逻辑查看数据库记录加入按站点和时间去重的判断
打卡时间不对时区设置不一致检查数据库中的时间字段统一为北京时间并保持 ISO 8601 格式
中文乱码终端编码或文件编码问题用命令行查看返回字符设置PYTHONIOENCODING=utf-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 数据建模的练手项目。

最容易踩的坑不在代码复杂度,而在数据规范。站点数据只要有一点坐标偏差或线路归类错误,地图展示就会很别扭。花费时间最多的往往不是写接口,而是整理北京地铁线路和站点的静态数据。

下一步我准备做的方向有三个:一是增加基于日历的足迹热力图,可以一眼看出每个月的通勤频率;二是让线路完成度统计支持“换乘站去重”和“全部换乘站算一次”两种模式,满足不同统计口径;三是补充一个更完整的自动化测试用例,把导入、去重、统计三个高风险模块用测试固定下来,方便后续继续加功能。

如果你也准备试一下,建议先跑通一键启动,然后立刻做一次批量导入导出测试。能把数据完整地导出来,这个工具的基本盘就稳了。后续再按自己的使用习惯去改前端界面和统计维度都很容易。

把项目做成开源,最大的收获不是代码本身,而是让有同样需求的人可以少走一些弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询