跳到正文
Hacker News· Vibeguy900·· 6 天前AI 评分34

Waymark:自托管的滚动地图故事编辑器

Interactive Maps for Storytelling

AI 导读

Waymark 是一款自托管编辑器,用于制作随滚动移动的地图故事,只需 Node.js 22.13 或更新版本,无需 npm install,git clone 后运行 npm start 即可使用。它内置 NOAA、NASA、USGS 等实时政府数据图层,支持筛选、缓冲区与六边形分箱等分析工具,并可绘制点线面标注。故事可发布为公开链接,或导出为纯静态网站文件,部署到任意 Web 服务器。

正文 · AI 翻译

Waymark: stories with a sense of place

MIT license Node 22.13 or newer Zero dependencies No API keys Docker ready

写一个故事。滚动它。发布到任何地方。
Waymark 是一个自托管的编辑器,用于制作滚动驱动的地图故事。
添加随阅读而移动的地图、实时政府数据和照片,然后分享链接或导出网站。

快速开始 · 功能 · 导出 · 自托管 · 故事格式

Scrolling through the Waymark welcome page

⚡ 一分钟开始

你只需要 Node.js 22.13 或更新版本。无需 npm install。

git clone https://github.com/chrispy90/waymark.git
cd waymark
npm start

然后打开 http://localhost:5180。

  1. 创建你的账户。 首次访问会让你成为所有者。
  2. 回答几个问题。 选择你要制作的内容和外观,添加标题和你的地点,Waymark 就会为你设置好故事。
  3. 点击任何内容即可编辑。 满意后,按 发布。

提示

Windows: 如果 PowerShell 提示 “running scripts is disabled”,请改为运行 node server.js。效果相同。 Docker: docker compose up -d 也可以,并将数据保存在命名卷中。

✨ 你可以制作什么

随故事移动的地图

当故事卡片滚动经过时,地图保持不动,并滑向每张卡片的位置。你可以倾斜、旋转、切换图层,或在任何卡片上将其替换为照片或视频。

A moving map flying between places as the reader scrolls

内置滚动效果

来自最佳长篇网站的效果,以区块形式提供。添加一个,它在手机上也能用。对于要求设备减少动态效果的读者,每个效果都会关闭。

你还会获得 章节(可重新着色页面)、大数字(可递增计数),以及 视差、缩放和擦除进入 的照片和封面。

来自 NOAA、NASA、USGS 等的实时数据

打开 浏览公共数据 并点击 添加。每个图层都已设置好样式,带有弹出窗口、图例和来源署名。无需密钥,无需注册。

The public data catalog

来源 图层
USGS 地震(实时)· 各州河流水位站
NOAA 天气预警(实时)· 雷达(实时)· 潮汐站 · 海啸 · 重大地震 · 火山 · 板块边界
NASA 自然事件(EONET)· 每日卫星影像 · 夜间地球
Census · NPS · USFS · EPA · NIFC 州和县 · 公园单元 · 国家森林 · 超级基金场地 · 活跃野火和火场边界
Natural Earth 国家 · 城市 · 河流
ArcGIS Online 搜索数千个更多公共图层

实时图层会在每次有人打开故事时刷新,因此已发布的地图保持最新。

This week's earthquakes and tectonic plate boundaries on a dark map

筛选它,然后分析它

筛选器 用简单规则缩小图层范围,例如 震级 ≥ 5、过去 7 天内 或 州 = 德克萨斯。它们适用于地图、洞察、分析和导出。

分析 会在你保留每个结果之前在地图上预览。工具包括缓冲区、六边形分箱、区域内计数、到最近点的距离、距离内、中心点、轮廓和筛选。结果会成为普通图层,你可以在新数据上重新运行它们。

你还可以将点显示为 热力图 或 聚类。也可以带上你自己的数据:GeoJSON、CSV 或任何公共 ArcGIS 图层。

直接在地图上绘制

在编辑器的任意地图上按 Draw,直接在上面绘制点、线、手绘路线、区域、方框、圆形和文字标签。为每个形状设置自己的颜色、填充、线宽、虚线或点线,以及箭头,再加上读者点击时看到的名称和描述。绘制时会实时测量距离和面积。选择一个形状即可移动、重新设计样式或复制它,描摹任何其他图层,并随时重新打开已保存的绘图。

The Draw workspace with a selected area, a dashed arrow and the style panel

一个不碍事的编辑器

点击实时预览中的任何内容即可编辑。你还可以拖动重新排序、撤销和重做、使用命令面板(Ctrl/⌘+K)、手机预览和自动保存。草稿在再次发布前保持私密。

Clicking a chapter in the preview to edit its title and color

大声表达:粗野主义风格

将任何故事切换为 Brutalist,即可获得原始等宽字体和厚重展示字体、硬朗的黑色边缘和偏移阴影、酸黄色贴纸、滚动标题、随滚动填充的描边文字,以及编号章节。或者从 Brutalist zine 模板开始。

A story in the brutalist theme on a laptop and two phones

每个故事都能在手机上运行

Three stories on phones: a satellite map, a statement block, and a stop-by-stop walk

从几个问题开始,而不是先删除一个演示

New story 会询问你在制作什么、它应该是什么样子、它的标题和封面,以及它发布到哪里。然后它会围绕你的地点和章节来构建故事,并在你撰写文字的地方提供简短提示。编辑器中的清单会引导你完成其余步骤:封面照片、你自己的文字、照片、手机检查和发布。

The New story wizard: story type, look, places, and the editor checklist

故事类型 你将获得
一段旅程 一张移动地图,每个地点一张卡片,外加整条路线的概览
一次徒步导览 带编号的站点,每个站点都有放置照片和描述的空间
一篇图文随笔 你的章节以全屏标题呈现,留有写作空间和一张照片
一个数据故事 一张大地图,可用于实时或上传的数据,并配有数字和背景信息
从零开始 一个标题和一个空白页面

每种类型都有五种外观。每种外观都会设定字体、颜色、章节颜色、地图样式和滚动动画。你可以稍后在 Story → Look & feel 下切换,并一键将现有章节和地图重新设计为匹配的样式。

The same statement block in the Classic, Night, Brutalist, Atlas and Aurora looks

外观 风格
Classic 温暖的纸张和衬线字体。Waymark 的原始外观
Night 黑暗而富有电影感,让照片和地图熠熠生辉
Brutalist 原始字体、硬朗边缘、响亮色彩和滚动标题
Atlas 一本旧地图集:羊皮纸、雕刻字体和小型大写字母、带罗盘玫瑰的双框棕褐色地图,以及按图版编号的章节
Aurora 深色玻璃和漂移的光线、发光的渐变字体、磨砂卡片和便当式数字。其地图上的点和线像萤火虫一样发光

更喜欢从成品中学习?Explore an example 会打开一个完整的故事供你拆解:移动地图、徒步、随笔、Brutalist zine 和 Down the Potomac 示例。任何故事还可以使用 Before & after,这是一个在同一视图中包含两张地图并带有可拖动分隔线的区块。

🚀 发布到任何地方

Publish 会为故事提供一个类似 /s/your-story 的公开链接,并在链接被分享时附带预览图。

Export as a website 会将故事转换为可在任何 Web 服务器上运行的纯文件,无需 Node、无需数据库,也无需 Waymark。

The export dialog with zip and Tomcat options

格式 用途
Website folder (.zip) Apache、nginx、IIS、共享主机、S3、GitHub Pages、Netlify,或者直接双击 index.html
Tomcat web app (.war) 把它放进 Tomcat 的 webapps/ 文件夹(Tomcat 8.5 到 11)

导出内容包括页面、你的图片、地图数据的快照,以及可选的地图库。所有路径都是相对路径,因此在任何文件夹中都能使用。你也可以从命令行导出:

node server.js export my-story --war --out /opt/tomcat/webapps/my-story.war

所有导出选项

node server.js stories                       # find the story's link or id
node server.js export my-story               # published version, as a .zip
node server.js export my-story --draft       # the unpublished draft

选项:--war、--draft、--out <file>、--no-images、--no-data 和 --no-vendor。最后三个将图片、数据或地图库保留为链接,而不是复制它们。只有地图瓦片和网络字体从互联网加载。

🏠 自行托管

你的故事、账户和图片都存放在一个 data/ 文件夹中。Waymark 可运行在任何具备 Node 22.13+ 或 Docker 以及持久磁盘的环境中:VPS、家用服务器、Fly.io、Railway、Render 等等。

  1. 将其置于 HTTPS 之后。 Caddy 是最简单的:caddy reverse-proxy --from stories.example.com --to localhost:5180
  2. 在 .env 中,设置 APP_URL=https://stories.example.com 和 COOKIE_SECURE=1。
  3. 账户创建完成后,设置 ALLOW_SIGNUP=0 以关闭注册。
  4. 备份 data/。它包含一切。

设置(.env)

将 .env.example 复制为 .env 并编辑它。所有内容都是可选的。

设置 默认值 作用
APP_NAME、APP_TAGLINE Waymark 重命名应用(界面、邮件、链接预览)
PORT 5180 监听的端口
HOST 仅本机 设为 0.0.0.0 以允许网络中的其他设备连接
APP_URL http://localhost:PORT 你站点的公开地址,用于密码重置邮件
DATA_DIR ./data 数据库(storymaps.db)和上传图片的存储位置
COOKIE_SECURE 关闭 当站点通过 HTTPS 提供服务时设为 1
ALLOW_SIGNUP 开启 账户创建完成后设为 0 以停止新注册
SMTP_* 关闭 用于密码重置的邮件。见下文

密码重置与邮件

人们可以从登录页面重置忘记的密码。要发送邮件,请在 .env 中设置 SMTP_* 设置。任何 SMTP 提供商都可以,例如 Postmark、Resend、SendGrid、Mailgun,或 Gmail 应用密码。

未设置邮件时,重置链接会打印在服务器运行的终端中。这对个人或本地使用来说没问题。你也可以从命令行自行重置密码:

npm run users                                 # list accounts
npm run reset-link -- someone@example.com     # print a reset link
npm run set-password -- someone@example.com   # type a new password directly

其他运行方式

npm run dev 编辑服务器代码时自动重启
npm test 针对临时数据库运行测试套件
docker compose up -d 在 Docker 中运行,数据保存在命名卷中

📚 底层原理

Waymark 是纯 JavaScript,没有构建步骤,也没有依赖。服务器仅使用 Node 的内置模块,包括 node:sqlite。浏览器应用是普通的 ES 模块。地图使用 MapLibre GL JS 在免费的 OpenFreeMap 和 Sentinel-2 底图上绘制。

项目结构

server.js              entry point + startup checks
server/index.js        HTTP server: API routes, uploads, published pages, static files
server/db.js           SQLite schema (built-in node:sqlite)
server/auth.js         password hashing, sessions, reset tokens, rate limiting
server/mail.js         minimal SMTP client
server/cli.js          admin commands
server/brand.js        app name and tagline
public/                the web app (plain ES modules, no build step)
  js/render.js         story → page; all block renderers
  js/effects.js        scroll-driven effects engine (one rAF loop, on-screen elements only)
  js/ui.js             icons, logo, toasts, menus, dialogs
  js/maps.js           MapLibre helpers: basemaps, views, data layers (incl. heatmap/clusters), legend, popups
  js/analysis.js       geospatial tools and layer statistics (Turf.js, loaded only in the editor)
  js/workbench.js      Add data, Analyze and Draw workspaces
  js/catalog.js        public data catalog (government sources + ArcGIS Online search)
  js/export-core.js    static-site / .war exporter (shared by the editor and `node server.js export`)
  js/export.js         the export dialog
  js/zip.js            minimal zip writer
  js/builder.js        the editor
  js/templates.js      starter templates
  welcome.html         landing page for signed-out visitors (itself a story)
  stories/, data/      sample story and sample GeoJSON
docs/SCHEMA.md         the story file format
test/                  end-to-end API tests (node:test)

API

每个更改数据的请求都必须发送请求头 X-Waymark: 1。这可以防止跨站请求伪造:其他站点无法在不进行 CORS 预检的情况下添加自定义请求头,而服务器从不允许预检。

POST /api/auth/signup {email, password, name} · POST /api/auth/login · POST /api/auth/logout · GET /api/me 账户
POST /api/auth/forgot {email} · GET /api/auth/reset/:token · POST /api/auth/reset {token, password} · POST /api/auth/password {current, password} 密码重置与更改
GET /api/stories · POST /api/stories {story} 列出并创建你的故事
GET /api/stories/:id · PUT /api/stories/:id {story, version} · DELETE /api/stories/:id 读取、保存和删除草稿。使用过期的 version 保存会返回 409
POST /api/stories/:id/publish {slug?} · POST /api/stories/:id/unpublish 发布将草稿复制到公开副本
GET /api/public/:slug 已发布故事的 JSON
POST /api/uploads(原始图片正文)· GET /api/uploads · DELETE /api/uploads/:id 图片:JPEG、PNG、WebP 或 GIF,按字节检查。限制 12 MB

安全

  • 密码与会话:密码使用 scrypt 哈希。会话是随机令牌,以哈希形式存储在 HttpOnly; SameSite=Lax cookie 中。登录、注册和重置请求都有限流。
  • 密码重置:重置链接一次性使用,一小时后过期,使用一个链接会登出所有现有会话。无论该邮箱是否有账户,重置请求都会得到相同的回应。链接始终基于 APP_URL 构建,绝不基于请求的 Host 头。
  • 故事内容:故事文本会根据 HTML 标签白名单进行清理。每个页面都会发送 Content-Security-Policy,仅允许运行来自本站点和 unpkg 的脚本,不允许内联脚本。
  • 上传:上传的文件会按其内容进行检查,这排除了 SVG,并以 nosniff 和 default-src 'none' 策略提供。

故事文件格式

故事是纯 JSON。参见 docs/SCHEMA.md。

🤝 贡献

欢迎提交错误报告、想法和拉取请求。请从 CONTRIBUTING.md 开始。请按照 SECURITY.md 中所述,私下报告安全问题。每个版本都列在 changelog 中。

背景

本项目最初是对 ArcGIS StoryMaps 阅读和创作体验的重建。它完全使用公开行为和开放格式从头编写。仅研究了 Esri 的开源经典模板(Map Journal、Map Series,Apache-2.0)和 mapbox/storytelling(BSD-3)的数据模型和 UX 模式。没有复制任何代码,也没有使用当前闭源的 ArcGIS StoryMaps 产品。

许可证

Waymark 在 MIT License 下发布。

它从 CDN 加载这些开源库(导出的故事可以捆绑 MapLibre 及其许可证): MapLibre GL JS(BSD-3-Clause),以及仅在编辑器中使用的 Turf.js(MIT)。

地图瓦片和数据来自其提供商,并在每张地图上注明:OpenStreetMap 贡献者和 OpenFreeMap(ODbL)、EOX 的 Sentinel-2 cloudless(CC-BY 4.0),以及 USGS、NOAA、NASA、US Census Bureau、NPS、USDA Forest Service、EPA、NIFC 和 Natural Earth(公有领域)。如果您为他人托管 Waymark,请保持这些署名可见。

来源:Hacker News · github.com