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 以进行快速预览
标签:CLI, GIS, OpenStreetMap, OSM, Python, WiFi技术, 个性化定制, 二进制发布, 代码生成艺术, 创意编程, 图片导出, 地图可视化, 地图生成器, 地理信息系统, 城市地图, 开源工具, 旅居纪念, 无后门, 极简主义, 海报打印, 海报设计, 自动化设计, 逆向工具