Spaces:
Sleeping
Sleeping
Migrated from GitHub
Browse files- .gitattributes +17 -0
- LICENSE +21 -0
- ORIGINAL_README.md +254 -0
- create_map_poster.py +471 -0
- fonts/Roboto-Bold.ttf +3 -0
- fonts/Roboto-Light.ttf +3 -0
- fonts/Roboto-Regular.ttf +3 -0
- posters/barcelona_warm_beige_20260108_172924.png +3 -0
- posters/chicago_noir_20260108_173313.png +3 -0
- posters/dubai_midnight_blue_20260108_174920.png +3 -0
- posters/marrakech_terracotta_20260108_180821.png +3 -0
- posters/melbourne_forest_20260108_181459.png +3 -0
- posters/mumbai_contrast_zones_20260108_170325.png +3 -0
- posters/mumbai_contrast_zones_20260108_172010.png +3 -0
- posters/new_york_noir_20260108_164217.png +3 -0
- posters/new_york_noir_20260108_172453.png +3 -0
- posters/san_francisco_sunset_20260108_184122.png +3 -0
- posters/singapore_neon_cyberpunk_20260108_184503.png +3 -0
- posters/tokyo_japanese_ink_20260108_165830.png +3 -0
- posters/venice_blueprint_20260108_165527.png +3 -0
- posters/washington_blueprint_20260108_184314.png +3 -0
- requirements.txt +29 -0
- themes/autumn.json +15 -0
- themes/blueprint.json +15 -0
- themes/contrast_zones.json +15 -0
- themes/copper_patina.json +15 -0
- themes/feature_based.json +15 -0
- themes/forest.json +15 -0
- themes/gradient_roads.json +15 -0
- themes/japanese_ink.json +15 -0
- themes/midnight_blue.json +15 -0
- themes/monochrome_blue.json +15 -0
- themes/neon_cyberpunk.json +15 -0
- themes/noir.json +15 -0
- themes/ocean.json +15 -0
- themes/pastel_dream.json +15 -0
- themes/sunset.json +15 -0
- themes/terracotta.json +15 -0
- themes/warm_beige.json +15 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,20 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
fonts/Roboto-Bold.ttf filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
fonts/Roboto-Light.ttf filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
fonts/Roboto-Regular.ttf filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
posters/barcelona_warm_beige_20260108_172924.png filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
posters/chicago_noir_20260108_173313.png filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
posters/dubai_midnight_blue_20260108_174920.png filter=lfs diff=lfs merge=lfs -text
|
| 42 |
+
posters/marrakech_terracotta_20260108_180821.png filter=lfs diff=lfs merge=lfs -text
|
| 43 |
+
posters/melbourne_forest_20260108_181459.png filter=lfs diff=lfs merge=lfs -text
|
| 44 |
+
posters/mumbai_contrast_zones_20260108_170325.png filter=lfs diff=lfs merge=lfs -text
|
| 45 |
+
posters/mumbai_contrast_zones_20260108_172010.png filter=lfs diff=lfs merge=lfs -text
|
| 46 |
+
posters/new_york_noir_20260108_164217.png filter=lfs diff=lfs merge=lfs -text
|
| 47 |
+
posters/new_york_noir_20260108_172453.png filter=lfs diff=lfs merge=lfs -text
|
| 48 |
+
posters/san_francisco_sunset_20260108_184122.png filter=lfs diff=lfs merge=lfs -text
|
| 49 |
+
posters/singapore_neon_cyberpunk_20260108_184503.png filter=lfs diff=lfs merge=lfs -text
|
| 50 |
+
posters/tokyo_japanese_ink_20260108_165830.png filter=lfs diff=lfs merge=lfs -text
|
| 51 |
+
posters/venice_blueprint_20260108_165527.png filter=lfs diff=lfs merge=lfs -text
|
| 52 |
+
posters/washington_blueprint_20260108_184314.png filter=lfs diff=lfs merge=lfs -text
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2026 Ankur Gupta
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
ORIGINAL_README.md
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# City Map Poster Generator
|
| 2 |
+
|
| 3 |
+
Generate beautiful, minimalist map posters for any city in the world.
|
| 4 |
+
|
| 5 |
+
<img src="posters/singapore_neon_cyberpunk_20260108_184503.png" width="250">
|
| 6 |
+
<img src="posters/dubai_midnight_blue_20260108_174920.png" width="250">
|
| 7 |
+
|
| 8 |
+
## Examples
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
| Country | City | Theme | Poster |
|
| 12 |
+
|:------------:|:--------------:|:---------------:|:------:|
|
| 13 |
+
| USA | San Francisco | sunset | <img src="posters/san_francisco_sunset_20260108_184122.png" width="250"> |
|
| 14 |
+
| Spain | Barcelona | warm_beige | <img src="posters/barcelona_warm_beige_20260108_172924.png" width="250"> |
|
| 15 |
+
| Italy | Venice | blueprint | <img src="posters/venice_blueprint_20260108_165527.png" width="250"> |
|
| 16 |
+
| Japan | Tokyo | japanese_ink | <img src="posters/tokyo_japanese_ink_20260108_165830.png" width="250"> |
|
| 17 |
+
| India | Mumbai | contrast_zones | <img src="posters/mumbai_contrast_zones_20260108_170325.png" width="250"> |
|
| 18 |
+
| Morocco | Marrakech | terracotta | <img src="posters/marrakech_terracotta_20260108_180821.png" width="250"> |
|
| 19 |
+
| Singapore | Singapore | neon_cyberpunk | <img src="posters/singapore_neon_cyberpunk_20260108_184503.png" width="250"> |
|
| 20 |
+
| Australia | Melbourne | forest | <img src="posters/melbourne_forest_20260108_181459.png" width="250"> |
|
| 21 |
+
| UAE | Dubai | midnight_blue | <img src="posters/dubai_midnight_blue_20260108_174920.png" width="250"> |
|
| 22 |
+
|
| 23 |
+
## Installation
|
| 24 |
+
|
| 25 |
+
```bash
|
| 26 |
+
pip install -r requirements.txt
|
| 27 |
+
```
|
| 28 |
+
|
| 29 |
+
## Usage
|
| 30 |
+
|
| 31 |
+
```bash
|
| 32 |
+
python create_map_poster.py --city <city> --country <country> [options]
|
| 33 |
+
```
|
| 34 |
+
|
| 35 |
+
### Options
|
| 36 |
+
|
| 37 |
+
| Option | Short | Description | Default |
|
| 38 |
+
|--------|-------|-------------|---------|
|
| 39 |
+
| `--city` | `-c` | City name | required |
|
| 40 |
+
| `--country` | `-C` | Country name | required |
|
| 41 |
+
| `--theme` | `-t` | Theme name | feature_based |
|
| 42 |
+
| `--distance` | `-d` | Map radius in meters | 29000 |
|
| 43 |
+
| `--list-themes` | | List all available themes | |
|
| 44 |
+
|
| 45 |
+
### Examples
|
| 46 |
+
|
| 47 |
+
```bash
|
| 48 |
+
# Iconic grid patterns
|
| 49 |
+
python create_map_poster.py -c "New York" -C "USA" -t noir -d 12000 # Manhattan grid
|
| 50 |
+
python create_map_poster.py -c "Barcelona" -C "Spain" -t warm_beige -d 8000 # Eixample district
|
| 51 |
+
|
| 52 |
+
# Waterfront & canals
|
| 53 |
+
python create_map_poster.py -c "Venice" -C "Italy" -t blueprint -d 4000 # Canal network
|
| 54 |
+
python create_map_poster.py -c "Amsterdam" -C "Netherlands" -t ocean -d 6000 # Concentric canals
|
| 55 |
+
python create_map_poster.py -c "Dubai" -C "UAE" -t midnight_blue -d 15000 # Palm & coastline
|
| 56 |
+
|
| 57 |
+
# Radial patterns
|
| 58 |
+
python create_map_poster.py -c "Paris" -C "France" -t pastel_dream -d 10000 # Haussmann boulevards
|
| 59 |
+
python create_map_poster.py -c "Moscow" -C "Russia" -t noir -d 12000 # Ring roads
|
| 60 |
+
|
| 61 |
+
# Organic old cities
|
| 62 |
+
python create_map_poster.py -c "Tokyo" -C "Japan" -t japanese_ink -d 15000 # Dense organic streets
|
| 63 |
+
python create_map_poster.py -c "Marrakech" -C "Morocco" -t terracotta -d 5000 # Medina maze
|
| 64 |
+
python create_map_poster.py -c "Rome" -C "Italy" -t warm_beige -d 8000 # Ancient layout
|
| 65 |
+
|
| 66 |
+
# Coastal cities
|
| 67 |
+
python create_map_poster.py -c "San Francisco" -C "USA" -t sunset -d 10000 # Peninsula grid
|
| 68 |
+
python create_map_poster.py -c "Sydney" -C "Australia" -t ocean -d 12000 # Harbor city
|
| 69 |
+
python create_map_poster.py -c "Mumbai" -C "India" -t contrast_zones -d 18000 # Coastal peninsula
|
| 70 |
+
|
| 71 |
+
# River cities
|
| 72 |
+
python create_map_poster.py -c "London" -C "UK" -t noir -d 15000 # Thames curves
|
| 73 |
+
python create_map_poster.py -c "Budapest" -C "Hungary" -t copper_patina -d 8000 # Danube split
|
| 74 |
+
|
| 75 |
+
# List available themes
|
| 76 |
+
python create_map_poster.py --list-themes
|
| 77 |
+
```
|
| 78 |
+
|
| 79 |
+
### Distance Guide
|
| 80 |
+
|
| 81 |
+
| Distance | Best for |
|
| 82 |
+
|----------|----------|
|
| 83 |
+
| 4000-6000m | Small/dense cities (Venice, Amsterdam center) |
|
| 84 |
+
| 8000-12000m | Medium cities, focused downtown (Paris, Barcelona) |
|
| 85 |
+
| 15000-20000m | Large metros, full city view (Tokyo, Mumbai) |
|
| 86 |
+
|
| 87 |
+
## Themes
|
| 88 |
+
|
| 89 |
+
17 themes available in `themes/` directory:
|
| 90 |
+
|
| 91 |
+
| Theme | Style |
|
| 92 |
+
|-------|-------|
|
| 93 |
+
| `feature_based` | Classic black & white with road hierarchy |
|
| 94 |
+
| `gradient_roads` | Smooth gradient shading |
|
| 95 |
+
| `contrast_zones` | High contrast urban density |
|
| 96 |
+
| `noir` | Pure black background, white roads |
|
| 97 |
+
| `midnight_blue` | Navy background with gold roads |
|
| 98 |
+
| `blueprint` | Architectural blueprint aesthetic |
|
| 99 |
+
| `neon_cyberpunk` | Dark with electric pink/cyan |
|
| 100 |
+
| `warm_beige` | Vintage sepia tones |
|
| 101 |
+
| `pastel_dream` | Soft muted pastels |
|
| 102 |
+
| `japanese_ink` | Minimalist ink wash style |
|
| 103 |
+
| `forest` | Deep greens and sage |
|
| 104 |
+
| `ocean` | Blues and teals for coastal cities |
|
| 105 |
+
| `terracotta` | Mediterranean warmth |
|
| 106 |
+
| `sunset` | Warm oranges and pinks |
|
| 107 |
+
| `autumn` | Seasonal burnt oranges and reds |
|
| 108 |
+
| `copper_patina` | Oxidized copper aesthetic |
|
| 109 |
+
| `monochrome_blue` | Single blue color family |
|
| 110 |
+
|
| 111 |
+
## Output
|
| 112 |
+
|
| 113 |
+
Posters are saved to `posters/` directory with format:
|
| 114 |
+
```
|
| 115 |
+
{city}_{theme}_{YYYYMMDD_HHMMSS}.png
|
| 116 |
+
```
|
| 117 |
+
|
| 118 |
+
## Adding Custom Themes
|
| 119 |
+
|
| 120 |
+
Create a JSON file in `themes/` directory:
|
| 121 |
+
|
| 122 |
+
```json
|
| 123 |
+
{
|
| 124 |
+
"name": "My Theme",
|
| 125 |
+
"description": "Description of the theme",
|
| 126 |
+
"bg": "#FFFFFF",
|
| 127 |
+
"text": "#000000",
|
| 128 |
+
"gradient_color": "#FFFFFF",
|
| 129 |
+
"water": "#C0C0C0",
|
| 130 |
+
"parks": "#F0F0F0",
|
| 131 |
+
"road_motorway": "#0A0A0A",
|
| 132 |
+
"road_primary": "#1A1A1A",
|
| 133 |
+
"road_secondary": "#2A2A2A",
|
| 134 |
+
"road_tertiary": "#3A3A3A",
|
| 135 |
+
"road_residential": "#4A4A4A",
|
| 136 |
+
"road_default": "#3A3A3A"
|
| 137 |
+
}
|
| 138 |
+
```
|
| 139 |
+
|
| 140 |
+
## Project Structure
|
| 141 |
+
|
| 142 |
+
```
|
| 143 |
+
map_poster/
|
| 144 |
+
├── create_map_poster.py # Main script
|
| 145 |
+
├── themes/ # Theme JSON files
|
| 146 |
+
├── fonts/ # Roboto font files
|
| 147 |
+
├── posters/ # Generated posters
|
| 148 |
+
└── README.md
|
| 149 |
+
```
|
| 150 |
+
|
| 151 |
+
## Hacker's Guide
|
| 152 |
+
|
| 153 |
+
Quick reference for contributors who want to extend or modify the script.
|
| 154 |
+
|
| 155 |
+
### Architecture Overview
|
| 156 |
+
|
| 157 |
+
```
|
| 158 |
+
┌─────────────────┐ ┌──────────────┐ ┌─────────────────┐
|
| 159 |
+
│ CLI Parser │────▶│ Geocoding │────▶│ Data Fetching │
|
| 160 |
+
│ (argparse) │ │ (Nominatim) │ │ (OSMnx) │
|
| 161 |
+
└─────────────────┘ └──────────────┘ └─────────────────┘
|
| 162 |
+
│
|
| 163 |
+
┌──────────────┐ ▼
|
| 164 |
+
│ Output │◀────┌─────────────────┐
|
| 165 |
+
│ (matplotlib)│ │ Rendering │
|
| 166 |
+
└──────────────┘ │ (matplotlib) │
|
| 167 |
+
└─────────────────┘
|
| 168 |
+
```
|
| 169 |
+
|
| 170 |
+
### Key Functions
|
| 171 |
+
|
| 172 |
+
| Function | Purpose | Modify when... |
|
| 173 |
+
|----------|---------|----------------|
|
| 174 |
+
| `get_coordinates()` | City → lat/lon via Nominatim | Switching geocoding provider |
|
| 175 |
+
| `create_poster()` | Main rendering pipeline | Adding new map layers |
|
| 176 |
+
| `get_edge_colors_by_type()` | Road color by OSM highway tag | Changing road styling |
|
| 177 |
+
| `get_edge_widths_by_type()` | Road width by importance | Adjusting line weights |
|
| 178 |
+
| `create_gradient_fade()` | Top/bottom fade effect | Modifying gradient overlay |
|
| 179 |
+
| `load_theme()` | JSON theme → dict | Adding new theme properties |
|
| 180 |
+
|
| 181 |
+
### Rendering Layers (z-order)
|
| 182 |
+
|
| 183 |
+
```
|
| 184 |
+
z=11 Text labels (city, country, coords)
|
| 185 |
+
z=10 Gradient fades (top & bottom)
|
| 186 |
+
z=3 Roads (via ox.plot_graph)
|
| 187 |
+
z=2 Parks (green polygons)
|
| 188 |
+
z=1 Water (blue polygons)
|
| 189 |
+
z=0 Background color
|
| 190 |
+
```
|
| 191 |
+
|
| 192 |
+
### OSM Highway Types → Road Hierarchy
|
| 193 |
+
|
| 194 |
+
```python
|
| 195 |
+
# In get_edge_colors_by_type() and get_edge_widths_by_type()
|
| 196 |
+
motorway, motorway_link → Thickest (1.2), darkest
|
| 197 |
+
trunk, primary → Thick (1.0)
|
| 198 |
+
secondary → Medium (0.8)
|
| 199 |
+
tertiary → Thin (0.6)
|
| 200 |
+
residential, living_street → Thinnest (0.4), lightest
|
| 201 |
+
```
|
| 202 |
+
|
| 203 |
+
### Adding New Features
|
| 204 |
+
|
| 205 |
+
**New map layer (e.g., railways):**
|
| 206 |
+
```python
|
| 207 |
+
# In create_poster(), after parks fetch:
|
| 208 |
+
try:
|
| 209 |
+
railways = ox.features_from_point(point, tags={'railway': 'rail'}, dist=dist)
|
| 210 |
+
except:
|
| 211 |
+
railways = None
|
| 212 |
+
|
| 213 |
+
# Then plot before roads:
|
| 214 |
+
if railways is not None and not railways.empty:
|
| 215 |
+
railways.plot(ax=ax, color=THEME['railway'], linewidth=0.5, zorder=2.5)
|
| 216 |
+
```
|
| 217 |
+
|
| 218 |
+
**New theme property:**
|
| 219 |
+
1. Add to theme JSON: `"railway": "#FF0000"`
|
| 220 |
+
2. Use in code: `THEME['railway']`
|
| 221 |
+
3. Add fallback in `load_theme()` default dict
|
| 222 |
+
|
| 223 |
+
### Typography Positioning
|
| 224 |
+
|
| 225 |
+
All text uses `transform=ax.transAxes` (0-1 normalized coordinates):
|
| 226 |
+
```
|
| 227 |
+
y=0.14 City name (spaced letters)
|
| 228 |
+
y=0.125 Decorative line
|
| 229 |
+
y=0.10 Country name
|
| 230 |
+
y=0.07 Coordinates
|
| 231 |
+
y=0.02 Attribution (bottom-right)
|
| 232 |
+
```
|
| 233 |
+
|
| 234 |
+
### Useful OSMnx Patterns
|
| 235 |
+
|
| 236 |
+
```python
|
| 237 |
+
# Get all buildings
|
| 238 |
+
buildings = ox.features_from_point(point, tags={'building': True}, dist=dist)
|
| 239 |
+
|
| 240 |
+
# Get specific amenities
|
| 241 |
+
cafes = ox.features_from_point(point, tags={'amenity': 'cafe'}, dist=dist)
|
| 242 |
+
|
| 243 |
+
# Different network types
|
| 244 |
+
G = ox.graph_from_point(point, dist=dist, network_type='drive') # roads only
|
| 245 |
+
G = ox.graph_from_point(point, dist=dist, network_type='bike') # bike paths
|
| 246 |
+
G = ox.graph_from_point(point, dist=dist, network_type='walk') # pedestrian
|
| 247 |
+
```
|
| 248 |
+
|
| 249 |
+
### Performance Tips
|
| 250 |
+
|
| 251 |
+
- Large `dist` values (>20km) = slow downloads + memory heavy
|
| 252 |
+
- Cache coordinates locally to avoid Nominatim rate limits
|
| 253 |
+
- Use `network_type='drive'` instead of `'all'` for faster renders
|
| 254 |
+
- Reduce `dpi` from 300 to 150 for quick previews
|
create_map_poster.py
ADDED
|
@@ -0,0 +1,471 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import osmnx as ox
|
| 2 |
+
import matplotlib.pyplot as plt
|
| 3 |
+
from matplotlib.font_manager import FontProperties
|
| 4 |
+
import matplotlib.colors as mcolors
|
| 5 |
+
import numpy as np
|
| 6 |
+
from geopy.geocoders import Nominatim
|
| 7 |
+
from tqdm import tqdm
|
| 8 |
+
import time
|
| 9 |
+
import json
|
| 10 |
+
import os
|
| 11 |
+
from datetime import datetime
|
| 12 |
+
import argparse
|
| 13 |
+
|
| 14 |
+
THEMES_DIR = "themes"
|
| 15 |
+
FONTS_DIR = "fonts"
|
| 16 |
+
POSTERS_DIR = "posters"
|
| 17 |
+
|
| 18 |
+
def load_fonts():
|
| 19 |
+
"""
|
| 20 |
+
Load Roboto fonts from the fonts directory.
|
| 21 |
+
Returns dict with font paths for different weights.
|
| 22 |
+
"""
|
| 23 |
+
fonts = {
|
| 24 |
+
'bold': os.path.join(FONTS_DIR, 'Roboto-Bold.ttf'),
|
| 25 |
+
'regular': os.path.join(FONTS_DIR, 'Roboto-Regular.ttf'),
|
| 26 |
+
'light': os.path.join(FONTS_DIR, 'Roboto-Light.ttf')
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
# Verify fonts exist
|
| 30 |
+
for weight, path in fonts.items():
|
| 31 |
+
if not os.path.exists(path):
|
| 32 |
+
print(f"⚠ Font not found: {path}")
|
| 33 |
+
return None
|
| 34 |
+
|
| 35 |
+
return fonts
|
| 36 |
+
|
| 37 |
+
FONTS = load_fonts()
|
| 38 |
+
|
| 39 |
+
def generate_output_filename(city, theme_name):
|
| 40 |
+
"""
|
| 41 |
+
Generate unique output filename with city, theme, and datetime.
|
| 42 |
+
"""
|
| 43 |
+
if not os.path.exists(POSTERS_DIR):
|
| 44 |
+
os.makedirs(POSTERS_DIR)
|
| 45 |
+
|
| 46 |
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
| 47 |
+
city_slug = city.lower().replace(' ', '_')
|
| 48 |
+
filename = f"{city_slug}_{theme_name}_{timestamp}.png"
|
| 49 |
+
return os.path.join(POSTERS_DIR, filename)
|
| 50 |
+
|
| 51 |
+
def get_available_themes():
|
| 52 |
+
"""
|
| 53 |
+
Scans the themes directory and returns a list of available theme names.
|
| 54 |
+
"""
|
| 55 |
+
if not os.path.exists(THEMES_DIR):
|
| 56 |
+
os.makedirs(THEMES_DIR)
|
| 57 |
+
return []
|
| 58 |
+
|
| 59 |
+
themes = []
|
| 60 |
+
for file in sorted(os.listdir(THEMES_DIR)):
|
| 61 |
+
if file.endswith('.json'):
|
| 62 |
+
theme_name = file[:-5] # Remove .json extension
|
| 63 |
+
themes.append(theme_name)
|
| 64 |
+
return themes
|
| 65 |
+
|
| 66 |
+
def load_theme(theme_name="feature_based"):
|
| 67 |
+
"""
|
| 68 |
+
Load theme from JSON file in themes directory.
|
| 69 |
+
"""
|
| 70 |
+
theme_file = os.path.join(THEMES_DIR, f"{theme_name}.json")
|
| 71 |
+
|
| 72 |
+
if not os.path.exists(theme_file):
|
| 73 |
+
print(f"⚠ Theme file '{theme_file}' not found. Using default feature_based theme.")
|
| 74 |
+
# Fallback to embedded default theme
|
| 75 |
+
return {
|
| 76 |
+
"name": "Feature-Based Shading",
|
| 77 |
+
"bg": "#FFFFFF",
|
| 78 |
+
"text": "#000000",
|
| 79 |
+
"gradient_color": "#FFFFFF",
|
| 80 |
+
"water": "#C0C0C0",
|
| 81 |
+
"parks": "#F0F0F0",
|
| 82 |
+
"road_motorway": "#0A0A0A",
|
| 83 |
+
"road_primary": "#1A1A1A",
|
| 84 |
+
"road_secondary": "#2A2A2A",
|
| 85 |
+
"road_tertiary": "#3A3A3A",
|
| 86 |
+
"road_residential": "#4A4A4A",
|
| 87 |
+
"road_default": "#3A3A3A"
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
with open(theme_file, 'r') as f:
|
| 91 |
+
theme = json.load(f)
|
| 92 |
+
print(f"✓ Loaded theme: {theme.get('name', theme_name)}")
|
| 93 |
+
if 'description' in theme:
|
| 94 |
+
print(f" {theme['description']}")
|
| 95 |
+
return theme
|
| 96 |
+
|
| 97 |
+
# Load theme (can be changed via command line or input)
|
| 98 |
+
THEME = None # Will be loaded later
|
| 99 |
+
|
| 100 |
+
def create_gradient_fade(ax, color, location='bottom', zorder=10):
|
| 101 |
+
"""
|
| 102 |
+
Creates a fade effect at the top or bottom of the map.
|
| 103 |
+
"""
|
| 104 |
+
vals = np.linspace(0, 1, 256).reshape(-1, 1)
|
| 105 |
+
gradient = np.hstack((vals, vals))
|
| 106 |
+
|
| 107 |
+
rgb = mcolors.to_rgb(color)
|
| 108 |
+
my_colors = np.zeros((256, 4))
|
| 109 |
+
my_colors[:, 0] = rgb[0]
|
| 110 |
+
my_colors[:, 1] = rgb[1]
|
| 111 |
+
my_colors[:, 2] = rgb[2]
|
| 112 |
+
|
| 113 |
+
if location == 'bottom':
|
| 114 |
+
my_colors[:, 3] = np.linspace(1, 0, 256)
|
| 115 |
+
extent_y_start = 0
|
| 116 |
+
extent_y_end = 0.25
|
| 117 |
+
else:
|
| 118 |
+
my_colors[:, 3] = np.linspace(0, 1, 256)
|
| 119 |
+
extent_y_start = 0.75
|
| 120 |
+
extent_y_end = 1.0
|
| 121 |
+
|
| 122 |
+
custom_cmap = mcolors.ListedColormap(my_colors)
|
| 123 |
+
|
| 124 |
+
xlim = ax.get_xlim()
|
| 125 |
+
ylim = ax.get_ylim()
|
| 126 |
+
y_range = ylim[1] - ylim[0]
|
| 127 |
+
|
| 128 |
+
y_bottom = ylim[0] + y_range * extent_y_start
|
| 129 |
+
y_top = ylim[0] + y_range * extent_y_end
|
| 130 |
+
|
| 131 |
+
ax.imshow(gradient, extent=[xlim[0], xlim[1], y_bottom, y_top],
|
| 132 |
+
aspect='auto', cmap=custom_cmap, zorder=zorder, origin='lower')
|
| 133 |
+
|
| 134 |
+
def get_edge_colors_by_type(G):
|
| 135 |
+
"""
|
| 136 |
+
Assigns colors to edges based on road type hierarchy.
|
| 137 |
+
Returns a list of colors corresponding to each edge in the graph.
|
| 138 |
+
"""
|
| 139 |
+
edge_colors = []
|
| 140 |
+
|
| 141 |
+
for u, v, data in G.edges(data=True):
|
| 142 |
+
# Get the highway type (can be a list or string)
|
| 143 |
+
highway = data.get('highway', 'unclassified')
|
| 144 |
+
|
| 145 |
+
# Handle list of highway types (take the first one)
|
| 146 |
+
if isinstance(highway, list):
|
| 147 |
+
highway = highway[0] if highway else 'unclassified'
|
| 148 |
+
|
| 149 |
+
# Assign color based on road type
|
| 150 |
+
if highway in ['motorway', 'motorway_link']:
|
| 151 |
+
color = THEME['road_motorway']
|
| 152 |
+
elif highway in ['trunk', 'trunk_link', 'primary', 'primary_link']:
|
| 153 |
+
color = THEME['road_primary']
|
| 154 |
+
elif highway in ['secondary', 'secondary_link']:
|
| 155 |
+
color = THEME['road_secondary']
|
| 156 |
+
elif highway in ['tertiary', 'tertiary_link']:
|
| 157 |
+
color = THEME['road_tertiary']
|
| 158 |
+
elif highway in ['residential', 'living_street', 'unclassified']:
|
| 159 |
+
color = THEME['road_residential']
|
| 160 |
+
else:
|
| 161 |
+
color = THEME['road_default']
|
| 162 |
+
|
| 163 |
+
edge_colors.append(color)
|
| 164 |
+
|
| 165 |
+
return edge_colors
|
| 166 |
+
|
| 167 |
+
def get_edge_widths_by_type(G):
|
| 168 |
+
"""
|
| 169 |
+
Assigns line widths to edges based on road type.
|
| 170 |
+
Major roads get thicker lines.
|
| 171 |
+
"""
|
| 172 |
+
edge_widths = []
|
| 173 |
+
|
| 174 |
+
for u, v, data in G.edges(data=True):
|
| 175 |
+
highway = data.get('highway', 'unclassified')
|
| 176 |
+
|
| 177 |
+
if isinstance(highway, list):
|
| 178 |
+
highway = highway[0] if highway else 'unclassified'
|
| 179 |
+
|
| 180 |
+
# Assign width based on road importance
|
| 181 |
+
if highway in ['motorway', 'motorway_link']:
|
| 182 |
+
width = 1.2
|
| 183 |
+
elif highway in ['trunk', 'trunk_link', 'primary', 'primary_link']:
|
| 184 |
+
width = 1.0
|
| 185 |
+
elif highway in ['secondary', 'secondary_link']:
|
| 186 |
+
width = 0.8
|
| 187 |
+
elif highway in ['tertiary', 'tertiary_link']:
|
| 188 |
+
width = 0.6
|
| 189 |
+
else:
|
| 190 |
+
width = 0.4
|
| 191 |
+
|
| 192 |
+
edge_widths.append(width)
|
| 193 |
+
|
| 194 |
+
return edge_widths
|
| 195 |
+
|
| 196 |
+
def get_coordinates(city, country):
|
| 197 |
+
"""
|
| 198 |
+
Fetches coordinates for a given city and country using geopy.
|
| 199 |
+
Includes rate limiting to be respectful to the geocoding service.
|
| 200 |
+
"""
|
| 201 |
+
print("Looking up coordinates...")
|
| 202 |
+
geolocator = Nominatim(user_agent="city_map_poster")
|
| 203 |
+
|
| 204 |
+
# Add a small delay to respect Nominatim's usage policy
|
| 205 |
+
time.sleep(1)
|
| 206 |
+
|
| 207 |
+
location = geolocator.geocode(f"{city}, {country}")
|
| 208 |
+
|
| 209 |
+
if location:
|
| 210 |
+
print(f"✓ Found: {location.address}")
|
| 211 |
+
print(f"✓ Coordinates: {location.latitude}, {location.longitude}")
|
| 212 |
+
return (location.latitude, location.longitude)
|
| 213 |
+
else:
|
| 214 |
+
raise ValueError(f"Could not find coordinates for {city}, {country}")
|
| 215 |
+
|
| 216 |
+
def create_poster(city, country, point, dist, output_file):
|
| 217 |
+
print(f"\nGenerating map for {city}, {country}...")
|
| 218 |
+
|
| 219 |
+
# Progress bar for data fetching
|
| 220 |
+
with tqdm(total=3, desc="Fetching map data", unit="step", bar_format='{l_bar}{bar}| {n_fmt}/{total_fmt}') as pbar:
|
| 221 |
+
# 1. Fetch Street Network
|
| 222 |
+
pbar.set_description("Downloading street network")
|
| 223 |
+
G = ox.graph_from_point(point, dist=dist, dist_type='bbox', network_type='all')
|
| 224 |
+
pbar.update(1)
|
| 225 |
+
time.sleep(0.5) # Rate limit between requests
|
| 226 |
+
|
| 227 |
+
# 2. Fetch Water Features
|
| 228 |
+
pbar.set_description("Downloading water features")
|
| 229 |
+
try:
|
| 230 |
+
water = ox.features_from_point(point, tags={'natural': 'water', 'waterway': 'riverbank'}, dist=dist)
|
| 231 |
+
except:
|
| 232 |
+
water = None
|
| 233 |
+
pbar.update(1)
|
| 234 |
+
time.sleep(0.3)
|
| 235 |
+
|
| 236 |
+
# 3. Fetch Parks
|
| 237 |
+
pbar.set_description("Downloading parks/green spaces")
|
| 238 |
+
try:
|
| 239 |
+
parks = ox.features_from_point(point, tags={'leisure': 'park', 'landuse': 'grass'}, dist=dist)
|
| 240 |
+
except:
|
| 241 |
+
parks = None
|
| 242 |
+
pbar.update(1)
|
| 243 |
+
|
| 244 |
+
print("✓ All data downloaded successfully!")
|
| 245 |
+
|
| 246 |
+
# 2. Setup Plot
|
| 247 |
+
print("Rendering map...")
|
| 248 |
+
fig, ax = plt.subplots(figsize=(12, 16), facecolor=THEME['bg'])
|
| 249 |
+
ax.set_facecolor(THEME['bg'])
|
| 250 |
+
ax.set_position([0, 0, 1, 1])
|
| 251 |
+
|
| 252 |
+
# 3. Plot Layers
|
| 253 |
+
# Layer 1: Polygons
|
| 254 |
+
if water is not None and not water.empty:
|
| 255 |
+
water.plot(ax=ax, facecolor=THEME['water'], edgecolor='none', zorder=1)
|
| 256 |
+
if parks is not None and not parks.empty:
|
| 257 |
+
parks.plot(ax=ax, facecolor=THEME['parks'], edgecolor='none', zorder=2)
|
| 258 |
+
|
| 259 |
+
# Layer 2: Roads with hierarchy coloring
|
| 260 |
+
print("Applying road hierarchy colors...")
|
| 261 |
+
edge_colors = get_edge_colors_by_type(G)
|
| 262 |
+
edge_widths = get_edge_widths_by_type(G)
|
| 263 |
+
|
| 264 |
+
ox.plot_graph(
|
| 265 |
+
G, ax=ax, bgcolor=THEME['bg'],
|
| 266 |
+
node_size=0,
|
| 267 |
+
edge_color=edge_colors,
|
| 268 |
+
edge_linewidth=edge_widths,
|
| 269 |
+
show=False, close=False
|
| 270 |
+
)
|
| 271 |
+
|
| 272 |
+
# Layer 3: Gradients (Top and Bottom)
|
| 273 |
+
create_gradient_fade(ax, THEME['gradient_color'], location='bottom', zorder=10)
|
| 274 |
+
create_gradient_fade(ax, THEME['gradient_color'], location='top', zorder=10)
|
| 275 |
+
|
| 276 |
+
# 4. Typography using Roboto font
|
| 277 |
+
if FONTS:
|
| 278 |
+
font_main = FontProperties(fname=FONTS['bold'], size=60)
|
| 279 |
+
font_top = FontProperties(fname=FONTS['bold'], size=40)
|
| 280 |
+
font_sub = FontProperties(fname=FONTS['light'], size=22)
|
| 281 |
+
font_coords = FontProperties(fname=FONTS['regular'], size=14)
|
| 282 |
+
else:
|
| 283 |
+
# Fallback to system fonts
|
| 284 |
+
font_main = FontProperties(family='monospace', weight='bold', size=60)
|
| 285 |
+
font_top = FontProperties(family='monospace', weight='bold', size=40)
|
| 286 |
+
font_sub = FontProperties(family='monospace', weight='normal', size=22)
|
| 287 |
+
font_coords = FontProperties(family='monospace', size=14)
|
| 288 |
+
|
| 289 |
+
spaced_city = " ".join(list(city.upper()))
|
| 290 |
+
|
| 291 |
+
# --- BOTTOM TEXT ---
|
| 292 |
+
ax.text(0.5, 0.14, spaced_city, transform=ax.transAxes,
|
| 293 |
+
color=THEME['text'], ha='center', fontproperties=font_main, zorder=11)
|
| 294 |
+
|
| 295 |
+
ax.text(0.5, 0.10, country.upper(), transform=ax.transAxes,
|
| 296 |
+
color=THEME['text'], ha='center', fontproperties=font_sub, zorder=11)
|
| 297 |
+
|
| 298 |
+
lat, lon = point
|
| 299 |
+
coords = f"{lat:.4f}° N / {lon:.4f}° E" if lat >= 0 else f"{abs(lat):.4f}° S / {lon:.4f}° E"
|
| 300 |
+
if lon < 0:
|
| 301 |
+
coords = coords.replace("E", "W")
|
| 302 |
+
|
| 303 |
+
ax.text(0.5, 0.07, coords, transform=ax.transAxes,
|
| 304 |
+
color=THEME['text'], alpha=0.7, ha='center', fontproperties=font_coords, zorder=11)
|
| 305 |
+
|
| 306 |
+
ax.plot([0.4, 0.6], [0.125, 0.125], transform=ax.transAxes,
|
| 307 |
+
color=THEME['text'], linewidth=1, zorder=11)
|
| 308 |
+
|
| 309 |
+
# --- ATTRIBUTION (bottom right) ---
|
| 310 |
+
if FONTS:
|
| 311 |
+
font_attr = FontProperties(fname=FONTS['light'], size=8)
|
| 312 |
+
else:
|
| 313 |
+
font_attr = FontProperties(family='monospace', size=8)
|
| 314 |
+
|
| 315 |
+
ax.text(0.98, 0.02, "© OpenStreetMap contributors", transform=ax.transAxes,
|
| 316 |
+
color=THEME['text'], alpha=0.5, ha='right', va='bottom',
|
| 317 |
+
fontproperties=font_attr, zorder=11)
|
| 318 |
+
|
| 319 |
+
# 5. Save
|
| 320 |
+
print(f"Saving to {output_file}...")
|
| 321 |
+
plt.savefig(output_file, dpi=300, facecolor=THEME['bg'])
|
| 322 |
+
plt.close()
|
| 323 |
+
print(f"✓ Done! Poster saved as {output_file}")
|
| 324 |
+
|
| 325 |
+
def print_examples():
|
| 326 |
+
"""Print usage examples."""
|
| 327 |
+
print("""
|
| 328 |
+
City Map Poster Generator
|
| 329 |
+
=========================
|
| 330 |
+
|
| 331 |
+
Usage:
|
| 332 |
+
python create_map_poster.py --city <city> --country <country> [options]
|
| 333 |
+
|
| 334 |
+
Examples:
|
| 335 |
+
# Iconic grid patterns
|
| 336 |
+
python create_map_poster.py -c "New York" -C "USA" -t noir -d 12000 # Manhattan grid
|
| 337 |
+
python create_map_poster.py -c "Barcelona" -C "Spain" -t warm_beige -d 8000 # Eixample district grid
|
| 338 |
+
|
| 339 |
+
# Waterfront & canals
|
| 340 |
+
python create_map_poster.py -c "Venice" -C "Italy" -t blueprint -d 4000 # Canal network
|
| 341 |
+
python create_map_poster.py -c "Amsterdam" -C "Netherlands" -t ocean -d 6000 # Concentric canals
|
| 342 |
+
python create_map_poster.py -c "Dubai" -C "UAE" -t midnight_blue -d 15000 # Palm & coastline
|
| 343 |
+
|
| 344 |
+
# Radial patterns
|
| 345 |
+
python create_map_poster.py -c "Paris" -C "France" -t pastel_dream -d 10000 # Haussmann boulevards
|
| 346 |
+
python create_map_poster.py -c "Moscow" -C "Russia" -t noir -d 12000 # Ring roads
|
| 347 |
+
|
| 348 |
+
# Organic old cities
|
| 349 |
+
python create_map_poster.py -c "Tokyo" -C "Japan" -t japanese_ink -d 15000 # Dense organic streets
|
| 350 |
+
python create_map_poster.py -c "Marrakech" -C "Morocco" -t terracotta -d 5000 # Medina maze
|
| 351 |
+
python create_map_poster.py -c "Rome" -C "Italy" -t warm_beige -d 8000 # Ancient street layout
|
| 352 |
+
|
| 353 |
+
# Coastal cities
|
| 354 |
+
python create_map_poster.py -c "San Francisco" -C "USA" -t sunset -d 10000 # Peninsula grid
|
| 355 |
+
python create_map_poster.py -c "Sydney" -C "Australia" -t ocean -d 12000 # Harbor city
|
| 356 |
+
python create_map_poster.py -c "Mumbai" -C "India" -t contrast_zones -d 18000 # Coastal peninsula
|
| 357 |
+
|
| 358 |
+
# River cities
|
| 359 |
+
python create_map_poster.py -c "London" -C "UK" -t noir -d 15000 # Thames curves
|
| 360 |
+
python create_map_poster.py -c "Budapest" -C "Hungary" -t copper_patina -d 8000 # Danube split
|
| 361 |
+
|
| 362 |
+
# List themes
|
| 363 |
+
python create_map_poster.py --list-themes
|
| 364 |
+
|
| 365 |
+
Options:
|
| 366 |
+
--city, -c City name (required)
|
| 367 |
+
--country, -C Country name (required)
|
| 368 |
+
--theme, -t Theme name (default: feature_based)
|
| 369 |
+
--distance, -d Map radius in meters (default: 29000)
|
| 370 |
+
--list-themes List all available themes
|
| 371 |
+
|
| 372 |
+
Distance guide:
|
| 373 |
+
4000-6000m Small/dense cities (Venice, Amsterdam old center)
|
| 374 |
+
8000-12000m Medium cities, focused downtown (Paris, Barcelona)
|
| 375 |
+
15000-20000m Large metros, full city view (Tokyo, Mumbai)
|
| 376 |
+
|
| 377 |
+
Available themes can be found in the 'themes/' directory.
|
| 378 |
+
Generated posters are saved to 'posters/' directory.
|
| 379 |
+
""")
|
| 380 |
+
|
| 381 |
+
def list_themes():
|
| 382 |
+
"""List all available themes with descriptions."""
|
| 383 |
+
available_themes = get_available_themes()
|
| 384 |
+
if not available_themes:
|
| 385 |
+
print("No themes found in 'themes/' directory.")
|
| 386 |
+
return
|
| 387 |
+
|
| 388 |
+
print("\nAvailable Themes:")
|
| 389 |
+
print("-" * 60)
|
| 390 |
+
for theme_name in available_themes:
|
| 391 |
+
theme_path = os.path.join(THEMES_DIR, f"{theme_name}.json")
|
| 392 |
+
try:
|
| 393 |
+
with open(theme_path, 'r') as f:
|
| 394 |
+
theme_data = json.load(f)
|
| 395 |
+
display_name = theme_data.get('name', theme_name)
|
| 396 |
+
description = theme_data.get('description', '')
|
| 397 |
+
except:
|
| 398 |
+
display_name = theme_name
|
| 399 |
+
description = ''
|
| 400 |
+
print(f" {theme_name}")
|
| 401 |
+
print(f" {display_name}")
|
| 402 |
+
if description:
|
| 403 |
+
print(f" {description}")
|
| 404 |
+
print()
|
| 405 |
+
|
| 406 |
+
if __name__ == "__main__":
|
| 407 |
+
parser = argparse.ArgumentParser(
|
| 408 |
+
description="Generate beautiful map posters for any city",
|
| 409 |
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
| 410 |
+
epilog="""
|
| 411 |
+
Examples:
|
| 412 |
+
python create_map_poster.py --city "New York" --country "USA"
|
| 413 |
+
python create_map_poster.py --city Tokyo --country Japan --theme midnight_blue
|
| 414 |
+
python create_map_poster.py --city Paris --country France --theme noir --distance 15000
|
| 415 |
+
python create_map_poster.py --list-themes
|
| 416 |
+
"""
|
| 417 |
+
)
|
| 418 |
+
|
| 419 |
+
parser.add_argument('--city', '-c', type=str, help='City name')
|
| 420 |
+
parser.add_argument('--country', '-C', type=str, help='Country name')
|
| 421 |
+
parser.add_argument('--theme', '-t', type=str, default='feature_based', help='Theme name (default: feature_based)')
|
| 422 |
+
parser.add_argument('--distance', '-d', type=int, default=29000, help='Map radius in meters (default: 29000)')
|
| 423 |
+
parser.add_argument('--list-themes', action='store_true', help='List all available themes')
|
| 424 |
+
|
| 425 |
+
args = parser.parse_args()
|
| 426 |
+
|
| 427 |
+
# If no arguments provided, show examples
|
| 428 |
+
if len(os.sys.argv) == 1:
|
| 429 |
+
print_examples()
|
| 430 |
+
os.sys.exit(0)
|
| 431 |
+
|
| 432 |
+
# List themes if requested
|
| 433 |
+
if args.list_themes:
|
| 434 |
+
list_themes()
|
| 435 |
+
os.sys.exit(0)
|
| 436 |
+
|
| 437 |
+
# Validate required arguments
|
| 438 |
+
if not args.city or not args.country:
|
| 439 |
+
print("Error: --city and --country are required.\n")
|
| 440 |
+
print_examples()
|
| 441 |
+
os.sys.exit(1)
|
| 442 |
+
|
| 443 |
+
# Validate theme exists
|
| 444 |
+
available_themes = get_available_themes()
|
| 445 |
+
if args.theme not in available_themes:
|
| 446 |
+
print(f"Error: Theme '{args.theme}' not found.")
|
| 447 |
+
print(f"Available themes: {', '.join(available_themes)}")
|
| 448 |
+
os.sys.exit(1)
|
| 449 |
+
|
| 450 |
+
print("=" * 50)
|
| 451 |
+
print("City Map Poster Generator")
|
| 452 |
+
print("=" * 50)
|
| 453 |
+
|
| 454 |
+
# Load theme
|
| 455 |
+
THEME = load_theme(args.theme)
|
| 456 |
+
|
| 457 |
+
# Get coordinates and generate poster
|
| 458 |
+
try:
|
| 459 |
+
coords = get_coordinates(args.city, args.country)
|
| 460 |
+
output_file = generate_output_filename(args.city, args.theme)
|
| 461 |
+
create_poster(args.city, args.country, coords, args.distance, output_file)
|
| 462 |
+
|
| 463 |
+
print("\n" + "=" * 50)
|
| 464 |
+
print("✓ Poster generation complete!")
|
| 465 |
+
print("=" * 50)
|
| 466 |
+
|
| 467 |
+
except Exception as e:
|
| 468 |
+
print(f"\n✗ Error: {e}")
|
| 469 |
+
import traceback
|
| 470 |
+
traceback.print_exc()
|
| 471 |
+
os.sys.exit(1)
|
fonts/Roboto-Bold.ttf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:61f89f8db49261c2f6106e8dccc35df7b2f7ed909020db40a3fc905e95f99334
|
| 3 |
+
size 514260
|
fonts/Roboto-Light.ttf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:028f843b1990ba46e2a1c4ef1b82729c4da9a946b0d9d8dbf59e623d1095e454
|
| 3 |
+
size 518580
|
fonts/Roboto-Regular.ttf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:56a45233d29f11b4dfb86d248e921939d115778f87325e7ae8cc108383d6664d
|
| 3 |
+
size 515100
|
posters/barcelona_warm_beige_20260108_172924.png
ADDED
|
Git LFS Details
|
posters/chicago_noir_20260108_173313.png
ADDED
|
Git LFS Details
|
posters/dubai_midnight_blue_20260108_174920.png
ADDED
|
Git LFS Details
|
posters/marrakech_terracotta_20260108_180821.png
ADDED
|
Git LFS Details
|
posters/melbourne_forest_20260108_181459.png
ADDED
|
Git LFS Details
|
posters/mumbai_contrast_zones_20260108_170325.png
ADDED
|
Git LFS Details
|
posters/mumbai_contrast_zones_20260108_172010.png
ADDED
|
Git LFS Details
|
posters/new_york_noir_20260108_164217.png
ADDED
|
Git LFS Details
|
posters/new_york_noir_20260108_172453.png
ADDED
|
Git LFS Details
|
posters/san_francisco_sunset_20260108_184122.png
ADDED
|
Git LFS Details
|
posters/singapore_neon_cyberpunk_20260108_184503.png
ADDED
|
Git LFS Details
|
posters/tokyo_japanese_ink_20260108_165830.png
ADDED
|
Git LFS Details
|
posters/venice_blueprint_20260108_165527.png
ADDED
|
Git LFS Details
|
posters/washington_blueprint_20260108_184314.png
ADDED
|
Git LFS Details
|
requirements.txt
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
certifi==2026.1.4
|
| 2 |
+
charset-normalizer==3.4.4
|
| 3 |
+
contourpy==1.3.3
|
| 4 |
+
cycler==0.12.1
|
| 5 |
+
fonttools==4.61.1
|
| 6 |
+
geographiclib==2.1
|
| 7 |
+
geopandas==1.1.2
|
| 8 |
+
geopy==2.4.1
|
| 9 |
+
idna==3.11
|
| 10 |
+
kiwisolver==1.4.9
|
| 11 |
+
matplotlib==3.10.8
|
| 12 |
+
networkx==3.6.1
|
| 13 |
+
numpy==2.4.0
|
| 14 |
+
osmnx==2.0.7
|
| 15 |
+
packaging==25.0
|
| 16 |
+
pandas==2.3.3
|
| 17 |
+
pillow==12.1.0
|
| 18 |
+
pyogrio==0.12.1
|
| 19 |
+
pyparsing==3.3.1
|
| 20 |
+
pyproj==3.7.2
|
| 21 |
+
python-dateutil==2.9.0.post0
|
| 22 |
+
pytz==2025.2
|
| 23 |
+
requests==2.32.5
|
| 24 |
+
scipy==1.16.3
|
| 25 |
+
shapely==2.1.2
|
| 26 |
+
six==1.17.0
|
| 27 |
+
tqdm==4.67.1
|
| 28 |
+
tzdata==2025.3
|
| 29 |
+
urllib3==2.6.3
|
themes/autumn.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "Autumn",
|
| 3 |
+
"description": "Burnt oranges, deep reds, golden yellows - seasonal warmth",
|
| 4 |
+
"bg": "#FBF7F0",
|
| 5 |
+
"text": "#8B4513",
|
| 6 |
+
"gradient_color": "#FBF7F0",
|
| 7 |
+
"water": "#D8CFC0",
|
| 8 |
+
"parks": "#E8E0D0",
|
| 9 |
+
"road_motorway": "#8B2500",
|
| 10 |
+
"road_primary": "#B8450A",
|
| 11 |
+
"road_secondary": "#CC7A30",
|
| 12 |
+
"road_tertiary": "#D9A050",
|
| 13 |
+
"road_residential": "#E8C888",
|
| 14 |
+
"road_default": "#CC7A30"
|
| 15 |
+
}
|
themes/blueprint.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "Blueprint",
|
| 3 |
+
"description": "Classic architectural blueprint - technical drawing aesthetic",
|
| 4 |
+
"bg": "#1A3A5C",
|
| 5 |
+
"text": "#E8F4FF",
|
| 6 |
+
"gradient_color": "#1A3A5C",
|
| 7 |
+
"water": "#0F2840",
|
| 8 |
+
"parks": "#1E4570",
|
| 9 |
+
"road_motorway": "#E8F4FF",
|
| 10 |
+
"road_primary": "#C5DCF0",
|
| 11 |
+
"road_secondary": "#9FC5E8",
|
| 12 |
+
"road_tertiary": "#7BAED4",
|
| 13 |
+
"road_residential": "#5A96C0",
|
| 14 |
+
"road_default": "#7BAED4"
|
| 15 |
+
}
|
themes/contrast_zones.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "Contrast Zones",
|
| 3 |
+
"description": "Strong contrast showing urban density - darker in center, lighter at edges",
|
| 4 |
+
"bg": "#FFFFFF",
|
| 5 |
+
"text": "#000000",
|
| 6 |
+
"gradient_color": "#FFFFFF",
|
| 7 |
+
"water": "#B0B0B0",
|
| 8 |
+
"parks": "#ECECEC",
|
| 9 |
+
"road_motorway": "#000000",
|
| 10 |
+
"road_primary": "#0F0F0F",
|
| 11 |
+
"road_secondary": "#252525",
|
| 12 |
+
"road_tertiary": "#404040",
|
| 13 |
+
"road_residential": "#5A5A5A",
|
| 14 |
+
"road_default": "#404040"
|
| 15 |
+
}
|
themes/copper_patina.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "Copper Patina",
|
| 3 |
+
"description": "Oxidized copper aesthetic - teal-green patina with copper accents",
|
| 4 |
+
"bg": "#E8F0F0",
|
| 5 |
+
"text": "#2A5A5A",
|
| 6 |
+
"gradient_color": "#E8F0F0",
|
| 7 |
+
"water": "#C0D8D8",
|
| 8 |
+
"parks": "#D8E8E0",
|
| 9 |
+
"road_motorway": "#B87333",
|
| 10 |
+
"road_primary": "#5A8A8A",
|
| 11 |
+
"road_secondary": "#6B9E9E",
|
| 12 |
+
"road_tertiary": "#88B4B4",
|
| 13 |
+
"road_residential": "#A8CCCC",
|
| 14 |
+
"road_default": "#88B4B4"
|
| 15 |
+
}
|
themes/feature_based.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "Feature-Based Shading",
|
| 3 |
+
"description": "Different shades for different road types and features with clear hierarchy",
|
| 4 |
+
"bg": "#FFFFFF",
|
| 5 |
+
"text": "#000000",
|
| 6 |
+
"gradient_color": "#FFFFFF",
|
| 7 |
+
"water": "#C0C0C0",
|
| 8 |
+
"parks": "#F0F0F0",
|
| 9 |
+
"road_motorway": "#0A0A0A",
|
| 10 |
+
"road_primary": "#1A1A1A",
|
| 11 |
+
"road_secondary": "#2A2A2A",
|
| 12 |
+
"road_tertiary": "#3A3A3A",
|
| 13 |
+
"road_residential": "#4A4A4A",
|
| 14 |
+
"road_default": "#3A3A3A"
|
| 15 |
+
}
|
themes/forest.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "Forest",
|
| 3 |
+
"description": "Deep greens and sage tones - organic botanical aesthetic",
|
| 4 |
+
"bg": "#F0F4F0",
|
| 5 |
+
"text": "#2D4A3E",
|
| 6 |
+
"gradient_color": "#F0F4F0",
|
| 7 |
+
"water": "#B8D4D4",
|
| 8 |
+
"parks": "#D4E8D4",
|
| 9 |
+
"road_motorway": "#2D4A3E",
|
| 10 |
+
"road_primary": "#3D6B55",
|
| 11 |
+
"road_secondary": "#5A8A70",
|
| 12 |
+
"road_tertiary": "#7AAA90",
|
| 13 |
+
"road_residential": "#A0C8B0",
|
| 14 |
+
"road_default": "#7AAA90"
|
| 15 |
+
}
|
themes/gradient_roads.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "Gradient Roads",
|
| 3 |
+
"description": "Smooth gradient from dark center to light edges with subtle features",
|
| 4 |
+
"bg": "#FFFFFF",
|
| 5 |
+
"text": "#000000",
|
| 6 |
+
"gradient_color": "#FFFFFF",
|
| 7 |
+
"water": "#D5D5D5",
|
| 8 |
+
"parks": "#EFEFEF",
|
| 9 |
+
"road_motorway": "#050505",
|
| 10 |
+
"road_primary": "#151515",
|
| 11 |
+
"road_secondary": "#2A2A2A",
|
| 12 |
+
"road_tertiary": "#404040",
|
| 13 |
+
"road_residential": "#555555",
|
| 14 |
+
"road_default": "#404040"
|
| 15 |
+
}
|
themes/japanese_ink.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "Japanese Ink",
|
| 3 |
+
"description": "Traditional ink wash inspired - minimalist with subtle red accent",
|
| 4 |
+
"bg": "#FAF8F5",
|
| 5 |
+
"text": "#2C2C2C",
|
| 6 |
+
"gradient_color": "#FAF8F5",
|
| 7 |
+
"water": "#E8E4E0",
|
| 8 |
+
"parks": "#F0EDE8",
|
| 9 |
+
"road_motorway": "#8B2500",
|
| 10 |
+
"road_primary": "#4A4A4A",
|
| 11 |
+
"road_secondary": "#6A6A6A",
|
| 12 |
+
"road_tertiary": "#909090",
|
| 13 |
+
"road_residential": "#B8B8B8",
|
| 14 |
+
"road_default": "#909090"
|
| 15 |
+
}
|
themes/midnight_blue.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "Midnight Blue",
|
| 3 |
+
"description": "Deep navy background with gold/copper roads - luxury atlas aesthetic",
|
| 4 |
+
"bg": "#0A1628",
|
| 5 |
+
"text": "#D4AF37",
|
| 6 |
+
"gradient_color": "#0A1628",
|
| 7 |
+
"water": "#061020",
|
| 8 |
+
"parks": "#0F2235",
|
| 9 |
+
"road_motorway": "#D4AF37",
|
| 10 |
+
"road_primary": "#C9A227",
|
| 11 |
+
"road_secondary": "#A8893A",
|
| 12 |
+
"road_tertiary": "#8B7355",
|
| 13 |
+
"road_residential": "#6B5B4F",
|
| 14 |
+
"road_default": "#8B7355"
|
| 15 |
+
}
|
themes/monochrome_blue.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "Monochrome Blue",
|
| 3 |
+
"description": "Single blue color family with varying saturation - clean and cohesive",
|
| 4 |
+
"bg": "#F5F8FA",
|
| 5 |
+
"text": "#1A3A5C",
|
| 6 |
+
"gradient_color": "#F5F8FA",
|
| 7 |
+
"water": "#D0E0F0",
|
| 8 |
+
"parks": "#E0EAF2",
|
| 9 |
+
"road_motorway": "#1A3A5C",
|
| 10 |
+
"road_primary": "#2A5580",
|
| 11 |
+
"road_secondary": "#4A7AA8",
|
| 12 |
+
"road_tertiary": "#7AA0C8",
|
| 13 |
+
"road_residential": "#A8C4E0",
|
| 14 |
+
"road_default": "#4A7AA8"
|
| 15 |
+
}
|
themes/neon_cyberpunk.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "Neon Cyberpunk",
|
| 3 |
+
"description": "Dark background with electric pink/cyan - bold night city vibes",
|
| 4 |
+
"bg": "#0D0D1A",
|
| 5 |
+
"text": "#00FFFF",
|
| 6 |
+
"gradient_color": "#0D0D1A",
|
| 7 |
+
"water": "#0A0A15",
|
| 8 |
+
"parks": "#151525",
|
| 9 |
+
"road_motorway": "#FF00FF",
|
| 10 |
+
"road_primary": "#00FFFF",
|
| 11 |
+
"road_secondary": "#00C8C8",
|
| 12 |
+
"road_tertiary": "#0098A0",
|
| 13 |
+
"road_residential": "#006870",
|
| 14 |
+
"road_default": "#0098A0"
|
| 15 |
+
}
|
themes/noir.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "Noir",
|
| 3 |
+
"description": "Pure black background with white/gray roads - modern gallery aesthetic",
|
| 4 |
+
"bg": "#000000",
|
| 5 |
+
"text": "#FFFFFF",
|
| 6 |
+
"gradient_color": "#000000",
|
| 7 |
+
"water": "#0A0A0A",
|
| 8 |
+
"parks": "#111111",
|
| 9 |
+
"road_motorway": "#FFFFFF",
|
| 10 |
+
"road_primary": "#E0E0E0",
|
| 11 |
+
"road_secondary": "#B0B0B0",
|
| 12 |
+
"road_tertiary": "#808080",
|
| 13 |
+
"road_residential": "#505050",
|
| 14 |
+
"road_default": "#808080"
|
| 15 |
+
}
|
themes/ocean.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "Ocean",
|
| 3 |
+
"description": "Various blues and teals - perfect for coastal cities",
|
| 4 |
+
"bg": "#F0F8FA",
|
| 5 |
+
"text": "#1A5F7A",
|
| 6 |
+
"gradient_color": "#F0F8FA",
|
| 7 |
+
"water": "#B8D8E8",
|
| 8 |
+
"parks": "#D8EAE8",
|
| 9 |
+
"road_motorway": "#1A5F7A",
|
| 10 |
+
"road_primary": "#2A7A9A",
|
| 11 |
+
"road_secondary": "#4A9AB8",
|
| 12 |
+
"road_tertiary": "#70B8D0",
|
| 13 |
+
"road_residential": "#A0D0E0",
|
| 14 |
+
"road_default": "#4A9AB8"
|
| 15 |
+
}
|
themes/pastel_dream.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "Pastel Dream",
|
| 3 |
+
"description": "Soft muted pastels with dusty blues and mauves - dreamy artistic aesthetic",
|
| 4 |
+
"bg": "#FAF7F2",
|
| 5 |
+
"text": "#5D5A6D",
|
| 6 |
+
"gradient_color": "#FAF7F2",
|
| 7 |
+
"water": "#D4E4ED",
|
| 8 |
+
"parks": "#E8EDE4",
|
| 9 |
+
"road_motorway": "#7B8794",
|
| 10 |
+
"road_primary": "#9BA4B0",
|
| 11 |
+
"road_secondary": "#B5AEBB",
|
| 12 |
+
"road_tertiary": "#C9C0C9",
|
| 13 |
+
"road_residential": "#D8D2D8",
|
| 14 |
+
"road_default": "#C9C0C9"
|
| 15 |
+
}
|
themes/sunset.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "Sunset",
|
| 3 |
+
"description": "Warm oranges and pinks on soft peach - dreamy golden hour aesthetic",
|
| 4 |
+
"bg": "#FDF5F0",
|
| 5 |
+
"text": "#C45C3E",
|
| 6 |
+
"gradient_color": "#FDF5F0",
|
| 7 |
+
"water": "#F0D8D0",
|
| 8 |
+
"parks": "#F8E8E0",
|
| 9 |
+
"road_motorway": "#C45C3E",
|
| 10 |
+
"road_primary": "#D87A5A",
|
| 11 |
+
"road_secondary": "#E8A088",
|
| 12 |
+
"road_tertiary": "#F0B8A8",
|
| 13 |
+
"road_residential": "#F5D0C8",
|
| 14 |
+
"road_default": "#E8A088"
|
| 15 |
+
}
|
themes/terracotta.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "Terracotta",
|
| 3 |
+
"description": "Mediterranean warmth - burnt orange and clay tones on cream",
|
| 4 |
+
"bg": "#F5EDE4",
|
| 5 |
+
"text": "#8B4513",
|
| 6 |
+
"gradient_color": "#F5EDE4",
|
| 7 |
+
"water": "#A8C4C4",
|
| 8 |
+
"parks": "#E8E0D0",
|
| 9 |
+
"road_motorway": "#A0522D",
|
| 10 |
+
"road_primary": "#B8653A",
|
| 11 |
+
"road_secondary": "#C9846A",
|
| 12 |
+
"road_tertiary": "#D9A08A",
|
| 13 |
+
"road_residential": "#E5C4B0",
|
| 14 |
+
"road_default": "#D9A08A"
|
| 15 |
+
}
|
themes/warm_beige.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "Warm Beige",
|
| 3 |
+
"description": "Earthy warm neutrals with sepia tones - vintage map aesthetic",
|
| 4 |
+
"bg": "#F5F0E8",
|
| 5 |
+
"text": "#6B5B4F",
|
| 6 |
+
"gradient_color": "#F5F0E8",
|
| 7 |
+
"water": "#DDD5C8",
|
| 8 |
+
"parks": "#E8E4D8",
|
| 9 |
+
"road_motorway": "#8B7355",
|
| 10 |
+
"road_primary": "#A08B70",
|
| 11 |
+
"road_secondary": "#B5A48E",
|
| 12 |
+
"road_tertiary": "#C9BBAA",
|
| 13 |
+
"road_residential": "#D9CFC2",
|
| 14 |
+
"road_default": "#C9BBAA"
|
| 15 |
+
}
|