originalankur/maptoposter
GitHub: originalankur/maptoposter
基于OpenStreetMap数据的城市路网海报生成器,支持多种美学主题、多语言排版和高分辨率输出。
Stars: 12058 | Forks: 1060
# 城市地图海报生成器
为世界上任何城市生成精美的极简主义地图海报。
## 示例
| 国家 | 城市 | 主题 | 海报 |
|:------------:|:--------------:|:---------------:|:------:|
| 美国 | 旧金山 | sunset |
|
| 西班牙 | 巴塞罗那 | warm_beige |
|
| 意大利 | 威尼斯 | blueprint |
|
| 日本 | 东京 | japanese_ink |
|
| 印度 | 孟买 | contrast_zones |
|
| 摩洛哥 | 马拉喀什 | terracotta |
|
| 新加坡 | 新加坡 | neon_cyberpunk |
|
| 澳大利亚 | 墨尔本 | forest |
|
| 阿联酋 | 迪拜 | midnight_blue |
|
| 美国 | 西雅图 | emerald |
|
## 安装
### 使用 uv (推荐)
确保已安装 [uv](https://docs.astral.sh/uv/)。通过在命令前添加 `uv run` 来运行脚本,它会自动创建并管理虚拟环境。
```
# 首次运行将自动安装依赖
uv run ./create_map_poster.py --city "Paris" --country "France"
# 或者先显式同步依赖(使用锁定版本)
uv sync --locked
uv run ./create_map_poster.py --city "Paris" --country "France"
```
### 使用 pip + venv
```
python -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
pip install -r requirements.txt
```
## 用法
### 生成海报
如果你正在使用 `uv`:
```
uv run ./create_map_poster.py --city --country [options]
```
否则 (pip + venv):
```
python create_map_poster.py --city --country [options]
```
### 必选选项
| 选项 | 简写 | 描述 |
|--------|-------|-------------|
| `--city` | `-c` | 城市名称 (用于地理编码) |
| `--country` | `-C` | 国家名称 (用于地理编码) |
### 可选参数
| 选项 | 简写 | 描述 | 默认值 |
|--------|-------|-------------|---------|
| **可选:** `--latitude` | `-lat` | 覆盖纬度中心点 (需与 --longitude 一起使用) | |
| **可选:** `--longitude` | `-long` | 覆盖经度中心点 (需与 --latitude 一起使用) | |
| **可选:** `--country-label` | | 覆盖海报上显示的国家文本 | |
| **可选:** `--theme` | `-t` | 主题名称 | terracotta |
| **可选:** `--distance` | `-d` | 地图半径 (米) | 18000 |
| **可选:** `--list-themes` | | 列出所有可用主题 | |
| **可选:** `--all-themes` | | 为所有可用主题生成海报 | |
| **可选:** `--width` | `-W` | 图像宽度 (英寸) | 12 (最大: 20) |
| **可选:** `--height` | `-H` | 图像高度 (英寸) | 16 (最大: 20) |
### 多语言支持 - i18n
使用 Google Fonts 的自定义字体以您的语言显示城市和国家名称:
| 选项 | 简写 | 描述 |
|--------|-------|-------------|
| `--display-city` | `-dc` | 城市的自定义显示名称 (例如: "东京") |
| `--display-country` | `-dC` | 国家的自定义显示名称 (例如: "日本") |
| `--font-family` | | Google Fonts 字体名称 (例如: "Noto Sans JP") |
**示例:**
```
# 日语
python create_map_poster.py -c "Tokyo" -C "Japan" -dc "東京" -dC "日本" --font-family "Noto Sans JP"
# 韩语
python create_map_poster.py -c "Seoul" -C "South Korea" -dc "서울" -dC "대한민국" --font-family "Noto Sans KR"
# 阿拉伯语
python create_map_poster.py -c "Dubai" -C "UAE" -dc "دبي" -dC "الإمارات" --font-family "Cairo"
```
**注意**: 字体会自动从 Google Fonts 下载并缓存在本地的 `fonts/cache/` 目录中。
### 分辨率指南 (300 DPI)
使用这些值为 `-W` 和 `-H` 以达到特定分辨率:
| 目标 | 分辨率 | 英寸 (-W / -H) |
|--------|-----------------|------------------|
| **Instagram 帖子** | 1080 x 1080 | 3.6 x 3.6 |
| **手机壁纸** | 1080 x 1920 | 3.6 x 6.4 |
| **HD 壁纸** | 1920 x 1080 | 6.4 x 3.6 |
| **4K 壁纸** | 3840 x 2160 | 12.8 x 7.2 |
| **A4 打印** | 2480 x 3508 | 8.3 x 11.7 |
### 使用示例
#### 基础示例
```
# 使用默认主题的简单用法
python create_map_poster.py -c "Paris" -C "France"
# 使用自定义主题和距离
python create_map_poster.py -c "New York" -C "USA" -t noir -d 12000
```
#### 多语言示例 (非拉丁文字)
以本地文字显示城市名称:
```
# 日语
python create_map_poster.py -c "Tokyo" -C "Japan" -dc "東京" -dC "日本" --font-family "Noto Sans JP" -t japanese_ink
# 韩语
python create_map_poster.py -c "Seoul" -C "South Korea" -dc "서울" -dC "대한민국" --font-family "Noto Sans KR" -t midnight_blue
# 泰语
python create_map_poster.py -c "Bangkok" -C "Thailand" -dc "กรุงเทพมหานคร" -dC "ประเทศไทย" --font-family "Noto Sans Thai" -t sunset
# 阿拉伯语
python create_map_poster.py -c "Dubai" -C "UAE" -dc "دبي" -dC "الإمارات" --font-family "Cairo" -t terracotta
# 中文(简体)
python create_map_poster.py -c "Beijing" -C "China" -dc "北京" -dC "中国" --font-family "Noto Sans SC"
# 高棉语
python create_map_poster.py -c "Phnom Penh" -C "Cambodia" -dc "ភ្នំពេញ" -dC "កម្ពុជា" --font-family "Noto Sans Khmer"
```
#### 高级示例
```
# 标志性网格图案
python create_map_poster.py -c "New York" -C "USA" -t noir -d 12000 # Manhattan grid
python create_map_poster.py -c "Barcelona" -C "Spain" -t warm_beige -d 8000 # Eixample district
# 滨水区和运河
python create_map_poster.py -c "Venice" -C "Italy" -t blueprint -d 4000 # Canal network
python create_map_poster.py -c "Amsterdam" -C "Netherlands" -t ocean -d 6000 # Concentric canals
python create_map_poster.py -c "Dubai" -C "UAE" -t midnight_blue -d 15000 # Palm & coastline
# 放射状图案
python create_map_poster.py -c "Paris" -C "France" -t pastel_dream -d 10000 # Haussmann boulevards
python create_map_poster.py -c "Moscow" -C "Russia" -t noir -d 12000 # Ring roads
# 有机老城
python create_map_poster.py -c "Tokyo" -C "Japan" -t japanese_ink -d 15000 # Dense organic streets
python create_map_poster.py -c "Marrakech" -C "Morocco" -t terracotta -d 5000 # Medina maze
python create_map_poster.py -c "Rome" -C "Italy" -t warm_beige -d 8000 # Ancient layout
# 沿海城市
python create_map_poster.py -c "San Francisco" -C "USA" -t sunset -d 10000 # Peninsula grid
python create_map_poster.py -c "Sydney" -C "Australia" -t ocean -d 12000 # Harbor city
python create_map_poster.py -c "Mumbai" -C "India" -t contrast_zones -d 18000 # Coastal peninsula
# 河流城市
python create_map_poster.py -c "London" -C "UK" -t noir -d 15000 # Thames curves
python create_map_poster.py -c "Budapest" -C "Hungary" -t copper_patina -d 8000 # Danube split
# 覆盖中心坐标
python create_map_poster.py --city "New York" --country "USA" -lat 40.776676 -long -73.971321 -t noir
# 列出可用主题
python create_map_poster.py --list-themes
# 为每个主题生成海报
python create_map_poster.py -c "Tokyo" -C "Japan" --all-themes
```
### 距离指南
| 距离 | 最适合 |
|----------|----------|
| 4000-6000m | 小型/密集城市 (威尼斯, 阿姆斯特丹市中心) |
| 8000-12000m | 中等城市, 聚焦市中心 (巴黎, 巴塞罗那) |
| 15000-20000m | 大型都市, 全城景观 (东京, 孟买) |
## 主题
`themes/` 目录中提供 17 种主题:
| 主题 | 风格 |
|-------|-------|
| `gradient_roads` | 平滑渐变阴影 |
| `contrast_zones` | 高对比度城市密度 |
| `noir` | 纯黑背景, 白色道路 |
| `midnight_blue` | 海军蓝背景配金色道路 |
| `blueprint` | 建筑蓝图美学 |
| `neon_cyberpunk` | 暗色背景配电光粉/青色 |
| `warm_beige` | 复古棕褐色调 |
| `pastel_dream` | 柔和的哑光粉彩 |
| `japanese_ink` | 极简水墨风格 |
| `emerald` | 葱郁的深绿美学 |
| `forest` | 深绿和鼠尾草绿 |
| `ocean` | 蓝色和青色, 适合沿海城市 |
| `terracotta` | 地中海暖色 |
| `sunset` | 暖橙色和粉色 |
| `autumn` | 季节性的焦橙色和红色 |
| `copper_patina` | 氧化铜美学 |
| `monochrome_blue` | 单一蓝色家族 |
## 输出
海报将保存到 `posters/` 目录,格式为:
```
{city}_{theme}_{YYYYMMDD_HHMMSS}.png
```
## 添加自定义主题
在 `themes/` 目录中创建一个 JSON 文件:
```
{
"name": "My Theme",
"description": "Description of the theme",
"bg": "#FFFFFF",
"text": "#000000",
"gradient_color": "#FFFFFF",
"water": "#C0C0C0",
"parks": "#F0F0F0",
"road_motorway": "#0A0A0A",
"road_primary": "#1A1A1A",
"road_secondary": "#2A2A2A",
"road_tertiary": "#3A3A3A",
"road_residential": "#4A4A4A",
"road_default": "#3A3A3A"
}
```
## 项目结构
```
map_poster/
├── create_map_poster.py # Main script
├── font_management.py # Font loading and Google Fonts integration
├── themes/ # Theme JSON files
├── fonts/ # Font files
│ ├── Roboto-*.ttf # Default Roboto fonts
│ └── cache/ # Downloaded Google Fonts (auto-generated)
├── posters/ # Generated posters
└── README.md
```
## 极客指南
供希望扩展或修改脚本的贡献者快速参考。
### 贡献者指南
- 欢迎修复 Bug
- 请勿提交用户界面 (Web/桌面端)
- 暂时不要 Docker 化
- 如果您凭直觉编写了修复代码,请先测试并查看修改前后的海报版本
- 在着手开发大型功能之前,请在讨论区/Issue 中询问是否会被合并
### 架构概览
```
┌─────────────────┐ ┌──────────────┐ ┌─────────────────┐
│ CLI Parser │────▶│ Geocoding │────▶│ Data Fetching │
│ (argparse) │ │ (Nominatim) │ │ (OSMnx) │
└─────────────────┘ └──────────────┘ └─────────────────┘
│
┌──────────────┐ ▼
│ Output │◀────┌─────────────────┐
│ (matplotlib)│ │ Rendering │
└──────────────┘ │ (matplotlib) │
└─────────────────┘
```
### 关键函数
| 函数 | 用途 | 修改时机... |
|----------|---------|----------------|
| `get_coordinates()` | 通过 Nominatim 将城市转换为经纬度 | 切换地理编码提供商时 |
| `create_poster()` | 主渲染管道 | 添加新地图图层时 |
| `get_edge_colors_by_type()` | 根据 OSM highway 标签确定道路颜色 | 更改道路样式时 |
| `get_edge_widths_by_type()` | 根据重要性确定道路宽度 | 调整线条粗细时 |
| `create_gradient_fade()` | 顶部/底部淡出效果 | 修改渐变覆盖层时 |
| `load_theme()` | 加载 JSON 主题为字典 | 添加新主题属性时 |
| `is_latin_script()` | 检测文字类型以调整排版 | 支持新文字系统时 |
| `load_fonts()` | 加载自定义/默认字体 | 更改字体加载逻辑时 |
### 渲染图层 (z-order)
```
z=11 Text labels (city, country, coords)
z=10 Gradient fades (top & bottom)
z=3 Roads (via ox.plot_graph)
z=2 Parks (green polygons)
z=1 Water (blue polygons)
z=0 Background color
```
### OSM Highway 类型 → 道路层级
```
# 在 get_edge_colors_by_type() 和 get_edge_widths_by_type() 中
motorway, motorway_link → Thickest (1.2), darkest
trunk, primary → Thick (1.0)
secondary → Medium (0.8)
tertiary → Thin (0.6)
residential, living_street → Thinnest (0.4), lightest
```
### 排版与文字检测
脚本会自动检测文字类型以应用适当的排版:
- **拉丁文字** (英语、法语、西班牙语等): 应用字母间距以实现优雅的 "P A R I S" 效果
- **非拉丁文字** (日语、阿拉伯语、泰语、韩语等): 自然间距,如 "东京" (字符间无间隙)
文字检测使用 Unicode 范围 (拉丁文为 U+0000-U+024F)。如果超过 80% 的字母字符属于拉丁文,则应用间距。
### 添加新功能
**新地图图层 (例如: 铁路):**
```
# 在 create_poster() 中,获取 parks 之后:
try:
railways = ox.features_from_point(point, tags={'railway': 'rail'}, dist=dist)
except:
railways = None
# 然后在 roads 之前绘图:
if railways is not None and not railways.empty:
railways = railways.to_crs(g_proj.graph["crs"])
railways.plot(ax=ax, color=THEME['railway'], linewidth=0.5, zorder=2.5)
```
**新主题属性:**
1. 添加到主题 JSON: `"railway": "#FF0000"`
2. 在代码中使用: `THEME['railway']`
3. 在 `load_theme()` 默认字典中添加回退值
### 排版定位
所有文本使用 `transform=ax.transAxes` (0-1 归一化坐标):
```
y=0.14 City name (spaced letters for Latin scripts)
y=0.125 Decorative line
y=0.10 Country name
y=0.07 Coordinates
y=0.02 Attribution (bottom-right)
```
### 实用的 OSMnx 模式
```
# 获取所有 buildings
buildings = ox.features_from_point(point, tags={'building': True}, dist=dist)
# 获取特定 amenities
cafes = ox.features_from_point(point, tags={'amenity': 'cafe'}, dist=dist)
# 不同的 network types
G = ox.graph_from_point(point, dist=dist, network_type='drive') # roads only
G = ox.graph_from_point(point, dist=dist, network_type='bike') # bike paths
G = ox.graph_from_point(point, dist=dist, network_type='walk') # pedestrian
```
### 性能技巧
- 较大的 `dist` 值 (>20km) = 下载慢 + 占用大量内存
- 在本地缓存坐标以避免 Nominatim 速率限制
- 使用 `network_type='drive'` 代替 `'all'` 以加快渲染速度
- 将 `dpi` 从 300 降低到 150 以进行快速预览
## 示例
| 国家 | 城市 | 主题 | 海报 |
|:------------:|:--------------:|:---------------:|:------:|
| 美国 | 旧金山 | sunset |
|
| 西班牙 | 巴塞罗那 | warm_beige |
|
| 意大利 | 威尼斯 | blueprint |
|
| 日本 | 东京 | japanese_ink |
|
| 印度 | 孟买 | contrast_zones |
|
| 摩洛哥 | 马拉喀什 | terracotta |
|
| 新加坡 | 新加坡 | neon_cyberpunk |
|
| 澳大利亚 | 墨尔本 | forest |
|
| 阿联酋 | 迪拜 | midnight_blue |
|
| 美国 | 西雅图 | emerald |
|
## 安装
### 使用 uv (推荐)
确保已安装 [uv](https://docs.astral.sh/uv/)。通过在命令前添加 `uv run` 来运行脚本,它会自动创建并管理虚拟环境。
```
# 首次运行将自动安装依赖
uv run ./create_map_poster.py --city "Paris" --country "France"
# 或者先显式同步依赖(使用锁定版本)
uv sync --locked
uv run ./create_map_poster.py --city "Paris" --country "France"
```
### 使用 pip + venv
```
python -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
pip install -r requirements.txt
```
## 用法
### 生成海报
如果你正在使用 `uv`:
```
uv run ./create_map_poster.py --city 标签:CLI, GIS, OpenStreetMap, OSM, Python, WiFi技术, 个性化定制, 二进制发布, 代码生成艺术, 创意编程, 图片导出, 地图可视化, 地图生成器, 地理信息系统, 城市地图, 开源工具, 旅居纪念, 无后门, 极简主义, 海报打印, 海报设计, 自动化设计, 逆向工具