├── .gitignore ├── requirements.txt ├── pics ├── msk_c.png ├── msk_black.png ├── msk_coral.png └── msk_white.png ├── map_poster_creator ├── config.py ├── __init__.py ├── logs.py ├── geojson.py ├── plotting.py ├── main.py ├── color_schemes.py └── entrypoints.py ├── setup.py ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Project exclude paths 2 | /venv/ -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | geopandas 2 | shapely 3 | matplotlib 4 | descartes -------------------------------------------------------------------------------- /pics/msk_c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k4m454k/MapPosterCreator/HEAD/pics/msk_c.png -------------------------------------------------------------------------------- /pics/msk_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k4m454k/MapPosterCreator/HEAD/pics/msk_black.png -------------------------------------------------------------------------------- /pics/msk_coral.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k4m454k/MapPosterCreator/HEAD/pics/msk_coral.png -------------------------------------------------------------------------------- /pics/msk_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k4m454k/MapPosterCreator/HEAD/pics/msk_white.png -------------------------------------------------------------------------------- /map_poster_creator/config.py: -------------------------------------------------------------------------------- 1 | # TODO: Add configs =) 2 | 3 | MAPOC_USER_PATH = "mapoc" 4 | USER_COLORS_SCHEME_FILE = "colors_scheme.json" 5 | -------------------------------------------------------------------------------- /map_poster_creator/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = 0.8 2 | __author__ = "Vadim Apenko" 3 | __author_email__ = "k4m454k@gmail.com" 4 | __telegram__ = "@k4m454k" 5 | -------------------------------------------------------------------------------- /map_poster_creator/logs.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | 4 | def log_processing(func): 5 | def wrapper(*args, **kwargs): 6 | logger = logging.getLogger(__name__) 7 | logger.info(f"Processing {func.__name__}...") 8 | value = func(*args, **kwargs) 9 | return value 10 | 11 | return wrapper -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | from map_poster_creator import __version__, __author__, __author_email__ 4 | 5 | with open("README.md", "r") as fh: 6 | long_description = fh.read() 7 | 8 | setuptools.setup( 9 | name="map_poster_creator", 10 | version=__version__, 11 | author=__author__, 12 | author_email=__author_email__, 13 | description="Map Poster Creator", 14 | long_description=long_description, 15 | long_description_content_type="text/markdown", 16 | url="https://github.com/k4m454k/MapPosterCreator", 17 | packages=setuptools.find_packages(), 18 | entry_points={ 19 | 'console_scripts': [ 20 | 'mapoc = map_poster_creator.entrypoints:map_poster', 21 | ], 22 | }, 23 | install_requires=open("requirements.txt").read().split(), 24 | python_requires='>=3.7', 25 | ) 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Vadim Apenko 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. 22 | -------------------------------------------------------------------------------- /map_poster_creator/geojson.py: -------------------------------------------------------------------------------- 1 | import json 2 | import logging 3 | from dataclasses import dataclass 4 | from typing import Dict, List 5 | 6 | from shapely.geometry import Polygon 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | 11 | @dataclass 12 | class MapGeometry: 13 | top: float 14 | bottom: float 15 | left: float 16 | right: float 17 | center: List[float] 18 | 19 | 20 | def get_polygon_from_geojson(geojson_path: str) -> Polygon: 21 | with open(geojson_path) as gjf: 22 | features: list = json.load(gjf).get("features") 23 | 24 | if not features: 25 | raise ValueError(f"Features not found in GeoJSON {geojson_path}") 26 | 27 | if len(features) > 1: 28 | logger.warning(f"Found {len(features)} features. Be use first") 29 | 30 | first_feature, *_ = features 31 | if not first_feature.get("type") == "Feature": 32 | raise ValueError(f"Invalid feature type {first_feature.get('type')}. Expected 'Feature'") 33 | 34 | geometry: dict = first_feature.get("geometry") 35 | if not geometry.get('type') == "Polygon": 36 | raise ValueError(f"Invalid geometry type {first_feature.get('type')}. Expected 'Polygon'") 37 | 38 | coordinates: list = geometry.get("coordinates") 39 | if not coordinates: 40 | raise ValueError(f"Coordinates not found. Check GeoJSON") 41 | 42 | if len(coordinates) > 1: 43 | logger.warning(f"Found {len(coordinates)} polygons. Be use first") 44 | 45 | first_coords, *_ = coordinates 46 | polygon = Polygon(first_coords) 47 | 48 | return polygon 49 | 50 | 51 | def get_map_geometry_from_poly(poly: Polygon) -> MapGeometry: 52 | x1, y1, x2, y2 = poly.bounds 53 | top = max(y1, y2) 54 | bottom = min(y1, y2) 55 | left = min(x1, x2) 56 | right = max(x1, x2) 57 | center = [(top + bottom) / 2, (left + right) / 2] 58 | geometry = MapGeometry(top=top, bottom=bottom, left=left, right=right, center=center) 59 | return geometry 60 | -------------------------------------------------------------------------------- /map_poster_creator/plotting.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | from geopandas import GeoDataFrame 4 | from matplotlib import pyplot as plt 5 | 6 | from map_poster_creator.geojson import MapGeometry 7 | from map_poster_creator.logs import log_processing 8 | 9 | 10 | def road_width(speed: int) -> float: 11 | if speed in range(0, 30): 12 | return 0.05 13 | if speed in range(30, 50): 14 | return 0.1 15 | if speed in range(50, 90): 16 | return 0.2 17 | if speed in range(90, 200): 18 | return 0.3 19 | return 0.4 20 | 21 | 22 | def plot_and_save( 23 | roads: GeoDataFrame, 24 | water: GeoDataFrame, 25 | greens: GeoDataFrame, 26 | color: dict, 27 | geometry: MapGeometry, 28 | path: str, 29 | dpi: int = 300, 30 | ) -> None: 31 | 32 | ax = set_subplot(color) 33 | 34 | plot_water(ax, color, water) 35 | 36 | plot_greens(ax, color, greens) 37 | 38 | plot_roads(ax, color, roads) 39 | 40 | aspect = 1 / math.cos(math.pi / 180 * geometry.center[0]) 41 | ax.set_aspect(aspect) 42 | ax.set_ylim((geometry.bottom, geometry.top)) 43 | ax.set_xlim((geometry.left, geometry.right)) 44 | plt.axis('off') 45 | save_image(dpi, path) 46 | 47 | 48 | @log_processing 49 | def save_image(dpi: int, path: str) -> None: 50 | plt.savefig(path, bbox_inches='tight', dpi=dpi) 51 | 52 | 53 | @log_processing 54 | def set_subplot(color: dict) -> plt.subplot: 55 | plt.clf() 56 | f, ax = plt.subplots(1, figsize=(19, 19), facecolor=color['facecolor']) 57 | return ax 58 | 59 | 60 | @log_processing 61 | def plot_water(ax: plt.subplot, color: dict, water: GeoDataFrame) -> None: 62 | water.plot(ax=ax, color=color['water'], linewidth=0.1) 63 | 64 | 65 | @log_processing 66 | def plot_greens(ax: plt.subplot, color: dict, greens: GeoDataFrame) -> None: 67 | greens.plot(ax=ax, color=color['greens'], linewidth=0.1) 68 | 69 | 70 | @log_processing 71 | def plot_roads(ax: plt.subplot, color: dict, roads: GeoDataFrame) -> None: 72 | roads.plot( 73 | ax=ax, 74 | color=color['roads'], 75 | linewidth=[road_width(d) for d in roads.speeds] 76 | ) 77 | -------------------------------------------------------------------------------- /map_poster_creator/main.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, List, Dict, Union 2 | 3 | import fiona 4 | from geopandas import GeoDataFrame 5 | from shapely.geometry import Polygon 6 | 7 | from map_poster_creator.color_schemes import get_color_schemes 8 | from map_poster_creator.geojson import get_polygon_from_geojson, get_map_geometry_from_poly, MapGeometry 9 | from map_poster_creator.logs import log_processing, logging 10 | from map_poster_creator.plotting import plot_and_save 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | 15 | @log_processing 16 | def get_roads_data(shp_path: str) -> GeoDataFrame: 17 | roads = GeoDataFrame.from_file(f"{shp_path}/gis_osm_roads_free_1.shp", encoding='utf-8') 18 | return roads # TODO: Add Path or os.path 19 | 20 | 21 | @log_processing 22 | def get_water_data(shp_path: str) -> GeoDataFrame: 23 | water = GeoDataFrame.from_file(f"{shp_path}/gis_osm_water_a_free_1.shp", encoding='utf-8') 24 | return water # TODO: Add Path or os.path 25 | 26 | 27 | @log_processing 28 | def get_greens_data(shp_path: str) -> GeoDataFrame: 29 | greens = GeoDataFrame.from_file(f"{shp_path}/gis_osm_pois_a_free_1.shp", encoding='utf-8') 30 | return greens # TODO: Add Path or os.path 31 | 32 | 33 | @log_processing 34 | def get_boundary_shape(geojson) -> Tuple[Polygon, MapGeometry]: 35 | poly = get_polygon_from_geojson(geojson) 36 | geometry = get_map_geometry_from_poly(poly) 37 | return poly, geometry 38 | 39 | 40 | @log_processing 41 | def preprocessing_roads(poly: Polygon, roads: GeoDataFrame) -> GeoDataFrame: 42 | town = roads.loc[roads['geometry'].apply(lambda g: poly.contains(g))].copy() 43 | town = town[~town.fclass.isin(['footway', "steps"])] 44 | town['speeds'] = [speed for speed in town['maxspeed']] 45 | return town 46 | 47 | 48 | @log_processing 49 | def preprocessing_other(poly: Polygon, dataframe: GeoDataFrame) -> GeoDataFrame: 50 | town = dataframe.loc[dataframe['geometry'].apply(lambda g: poly.contains(g))].copy() 51 | return town 52 | 53 | 54 | def create_poster( 55 | base_shp_path: str, 56 | geojson_path: str, 57 | colors: Union[List[Union[Dict, str]], None], 58 | layers: List[str], 59 | config: dict, 60 | output_prefix: str, 61 | user_color_scheme: bool = False, 62 | ): 63 | poly, geometry = get_boundary_shape(geojson=geojson_path) 64 | roads = get_roads_data(base_shp_path) 65 | water = get_water_data(base_shp_path) 66 | greens = get_greens_data(base_shp_path) 67 | roads_df = preprocessing_roads(poly=poly, roads=roads) 68 | water_df = preprocessing_other(poly=poly, dataframe=water) 69 | greens_df = preprocessing_other(poly=poly, dataframe=greens) 70 | # TODO: Support user color scheme 71 | for color in colors: 72 | if color not in get_color_schemes().keys(): 73 | logger.warning(f"Color {color} not found in base color scheme. " 74 | f"Available colors: {', '.join(get_color_schemes().keys())}") 75 | print("") 76 | print(f"Plot and Save {color} map") 77 | plot_and_save( 78 | roads=roads_df, 79 | water=water, 80 | greens=greens_df, 81 | geometry=geometry, 82 | path=f'{output_prefix}_{color}.png', 83 | dpi=1200, 84 | color=get_color_schemes()[color] 85 | ) 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Map Poster Creator 2 | 3 | **Map Poster Creator** - script for creating beautiful road maps of any cities, zones, sections according to OSM data. 4 | You can add green areas, roads, rivers, ponds, lakes to the map. 5 | There are several ready-made color schemes, but you can easily add your own colors. 6 | 7 | The project is provided as-is. 8 | 9 | ![main window](https://raw.githubusercontent.com/k4m454k/MapPosterCreator/master/pics/msk_c.png?raw=true) 10 | 11 | 12 | ## Valriable Colors 13 | 14 | ### `white` 15 | ![white](https://raw.githubusercontent.com/k4m454k/MapPosterCreator/master/pics/msk_white.png?raw=true) 16 | 17 | ### `black` 18 | ![black](https://raw.githubusercontent.com/k4m454k/MapPosterCreator/master/pics/msk_black.png?raw=true) 19 | 20 | ### `coral` 21 | ![coral](https://raw.githubusercontent.com/k4m454k/MapPosterCreator/master/pics/msk_coral.png?raw=true) 22 | 23 | 24 | ## Install: 25 | 26 | `pip install map-poster-creator` 27 | 28 | ### Deps 29 | 30 | #### Linux 31 | - `apt-get install libgeos-dev` 32 | 33 | #### Windows 34 | thanks [Lamroy95](https://github.com/Lamroy95) for Windows instruction 35 | - Manually download and install two python packages (GDAL and Fiona): 36 | - Download [GDAL .whl file](https://www.lfd.uci.edu/~gohlke/pythonlibs/#gdal) for your version of python (Python 3.8 - ...cp38....whl) 37 | - Download [Fiona .whl file](https://www.lfd.uci.edu/~gohlke/pythonlibs/#fiona) 38 | - Install GDAL: `pip install path\to\gdal.whl` 39 | - Install Fiona: `pip install path\to\fiona.whl` 40 | - Finally, install map-poster-creator: `pip install map-poster-creator` 41 | - Or just use Docker =) 42 | 43 | #### MacOS 44 | - `brew install geos` 45 | 46 | ## Usage: 47 | 48 | 1. Create geojson file with one poly. https://geojson.io/ 49 | 2. Download shp archive for region https://download.geofabrik.de/ eq: `central-fed-district-latest-free.shp.zip` 50 | 3. Unpack `*.free.shp.zip` archive to some folder `PATH_TO_SHP_DIR` 51 | 52 | ```bash 53 | $ mapoc poster create --shp_path PATH_TO_SHP_DIR --geojson PATH_TO_GEOJSON --colors white black coral 54 | ``` 55 | 56 | ```bash 57 | $ mapoc poster create -h 58 | usage: Map Poster Creator poster create [-h] --shp_path SHP_PATH --geojson GEOJSON [--colors COLORS [COLORS ...]] [--output_prefix OUTPUT_PREFIX] 59 | 60 | Make Poster 61 | 62 | optional arguments: 63 | -h, --help show this help message and exit 64 | --shp_path SHP_PATH Path to shp folder.type "mapoc misc shp" to download 65 | --geojson GEOJSON Path to geojson file with boundary polygon.type "mapoc misc geojson" to create and download 66 | --colors COLORS [COLORS ...] 67 | Provide colors. eq "--colors white black coral". Default: "white". Available colors: black, white, red, coral 68 | --output_prefix OUTPUT_PREFIX 69 | Output file prefix. eq. "{OUTPUT_PREFIX}_{COLOR}.png". Default: "map" 70 | ``` 71 | 72 | ```bash 73 | $ mapoc misc -h 74 | usage: Map Poster Creator misc [-h] {shp,geojson} ... 75 | 76 | Misc services 77 | 78 | optional arguments: 79 | -h, --help show this help message and exit 80 | 81 | misc management commands: 82 | misc 83 | 84 | {shp,geojson} Additional help for available commands 85 | 86 | ``` 87 | 88 | ### Colors 89 | 90 | #### Add new color scheme 91 | 92 | Add a new color scheme or rewrite available color scheme. 93 | 94 | ```bash 95 | usage: mapoc color add [-h] --name NAME --facecolor FACECOLOR --water WATER --greens GREENS --roads ROADS 96 | 97 | List available colors 98 | 99 | optional arguments: 100 | -h, --help show this help message and exit 101 | --name NAME Name of color scheme. eq. "blue" 102 | --facecolor FACECOLOR 103 | MatPlot face hex color. eq. "#ffffff" 104 | --water WATER MatPlot water hex color. eq. "#ffffff" 105 | --greens GREENS MatPlot greens hex color. eq. "#ffffff" 106 | --roads ROADS MatPlot roads hex color. eq. "#ffffff" 107 | 108 | ``` 109 | 110 | Example: 111 | ```bash 112 | $ mapoc color add --name "coffee" --facecolor "#433633" --water "#5c5552" --greens "#8f857d" --roads "#decbb7" 113 | ``` 114 | 115 | #### List available color schemes 116 | 117 | ```bash 118 | $ mapoc color list 119 | ``` 120 | 121 | ## TODO 122 | 123 | - Add configurable settings for poster size and quality. 124 | - Add Docker image 125 | -------------------------------------------------------------------------------- /map_poster_creator/color_schemes.py: -------------------------------------------------------------------------------- 1 | import json 2 | import logging 3 | import os 4 | import re 5 | from pathlib import Path 6 | from typing import Union, Tuple, List, Dict 7 | 8 | from map_poster_creator.config import MAPOC_USER_PATH, USER_COLORS_SCHEME_FILE 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | base_color_scheme = { 13 | "META_INFO": { 14 | "config_version": 1, 15 | "defaults": { 16 | "facecolor": [0, 0, 0], 17 | "water": "#383d52", 18 | "greens": "#354038", 19 | "roads": "#ffffff", 20 | } 21 | }, 22 | "black": { 23 | "facecolor": [0, 0, 0], 24 | "water": "#383d52", 25 | "greens": "#354038", 26 | "roads": "#ffffff", 27 | }, 28 | "white": { 29 | "facecolor": [1, 1, 1], 30 | "water": "#bdddff", 31 | "greens": "#d4ffe1", 32 | "roads": "#000000", 33 | }, 34 | "red": { 35 | "facecolor": [0.4, 0.12, 0.12], 36 | "water": "#754444", 37 | "greens": "#b36969", 38 | "roads": "#ffffff", 39 | }, 40 | "coral": { 41 | "facecolor": [0.67, 0.2, 0.18], 42 | "water": "#ffffff", 43 | "greens": "#b36969", 44 | "roads": "#ffffff", 45 | }, 46 | } 47 | 48 | 49 | def is_hex_color(hex_color: str) -> bool: 50 | match = re.search(r'^#(?:[0-9a-fA-F]{3}){1,2}$', hex_color) 51 | return True if match else False 52 | 53 | 54 | def hex_to_faceacolor(hex_color: str) -> Tuple[float]: 55 | if not is_hex_color(hex_color): 56 | raise ValueError(f"{hex_color} is a not hex color. Expected eq. '#ffffff'") 57 | 58 | color = hex_color.strip("#") 59 | return tuple(1 / 255 * int(color[i:i+2], 16) for i in (0, 2, 4)) # magic asshole-code 60 | 61 | 62 | def get_color_schemes() -> Dict[str, Dict[str, Union[List[float], Tuple[float]]]]: 63 | config_path = Path(os.path.expanduser("~")) / MAPOC_USER_PATH / USER_COLORS_SCHEME_FILE 64 | ensure_user_colors_or_create(config_path) 65 | update_user_colors_if_need(config_path) 66 | with open(config_path, "r") as conf: 67 | color_scheme = json.load(conf) 68 | 69 | return color_scheme 70 | 71 | 72 | def ensure_user_colors_or_create(config_path: Path) -> None: 73 | if config_path.is_file(): 74 | return 75 | if not config_path.parent.is_dir(): 76 | os.makedirs(config_path.parent) 77 | 78 | logger.info(f"User colors config not found!") 79 | save_user_color_schemes(config_path=config_path, color_schemes=base_color_scheme) 80 | 81 | 82 | def update_user_colors_if_need(config_path: Path): 83 | # TODO: Add logic for update config version 84 | pass 85 | 86 | 87 | def save_user_color_schemes( 88 | config_path: Path, 89 | color_schemes: Dict[str, Dict[str, Union[List[float], Tuple[float]]]], 90 | ) -> None: 91 | with open(config_path, "w") as conf: 92 | json.dump(color_schemes, conf) 93 | logger.info(f"Save user colors config: {config_path}") 94 | 95 | 96 | def compose_user_color_scheme( 97 | name: str, 98 | facecolor: Union[Tuple[Union[float, int]], List[Union[float, int]], str], 99 | water: str, 100 | greens: str, 101 | roads: str 102 | ) -> Dict[str, Dict[str, Union[List[float], Tuple[float]]]]: 103 | if isinstance(facecolor, str): 104 | if not is_hex_color(facecolor): 105 | raise ValueError(f"{facecolor} is a not hex color. Expected eq. '#ffffff'") 106 | facecolor = hex_to_faceacolor(facecolor) 107 | 108 | if not all([is_hex_color(val) for val in [water, greens, roads]]): 109 | raise ValueError(f"Hex color validation error. Expected eq. '#ffffff'") 110 | 111 | new_scheme = { 112 | name: { 113 | "facecolor": facecolor, 114 | "water": water, 115 | "greens": greens, 116 | "roads": roads, 117 | } 118 | } 119 | return new_scheme 120 | 121 | 122 | def add_user_color_scheme( 123 | name: str, 124 | facecolor: Union[Tuple[Union[float, int]], List[Union[float, int]], str], 125 | water: str, 126 | greens: str, 127 | roads: str 128 | ) -> None: 129 | current_color_schemes = get_color_schemes() 130 | if name in current_color_schemes.keys(): 131 | logger.warning(f"Color scheme {name} exists. Will be rewrite.") 132 | 133 | new_scheme = compose_user_color_scheme( 134 | name=name, 135 | facecolor=facecolor, 136 | water=water, 137 | greens=greens, 138 | roads=roads, 139 | ) 140 | 141 | config_path = Path(os.path.expanduser("~")) / MAPOC_USER_PATH / USER_COLORS_SCHEME_FILE 142 | current_color_schemes.update(new_scheme) 143 | save_user_color_schemes(config_path=config_path, color_schemes=current_color_schemes) 144 | -------------------------------------------------------------------------------- /map_poster_creator/entrypoints.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import logging 3 | import webbrowser 4 | from pprint import pprint 5 | 6 | from map_poster_creator.color_schemes import base_color_scheme, get_color_schemes, add_user_color_scheme 7 | from map_poster_creator.main import create_poster 8 | from map_poster_creator import __version__ 9 | 10 | logging.basicConfig( 11 | level=logging.INFO, 12 | format='%(asctime)s %(levelname)-8s %(message)s', 13 | datefmt='%Y-%m-%d %H:%M:%S' 14 | ) 15 | 16 | 17 | def get_root_parser() -> argparse.ArgumentParser: 18 | parser = argparse.ArgumentParser(prog='mapoc', description="Map Poster Creator") 19 | parser.add_argument('-v', '--version', action='version', version='%(prog)s ' + str(__version__)) 20 | 21 | return parser 22 | 23 | 24 | def add_poster_create_subparser(parent_parser) -> argparse.ArgumentParser: 25 | poster_create_parser = parent_parser.add_parser('create', description='Make Poster') 26 | poster_create_parser.add_argument('--shp_path', help='Path to shp folder. ' 27 | 'Type "mapoc misc shp" to download.', required=True) 28 | poster_create_parser.add_argument( 29 | '--geojson', help='Path to geojson file with boundary polygon. ' 30 | 'Type "mapoc misc geojson" to create and download.', 31 | required=True, 32 | ) 33 | poster_create_parser.add_argument( 34 | '--colors', help=f'Provide colors. ' 35 | f'eq "--colors white black coral". ' 36 | f'Default: "white". ' 37 | f'Available colors: {", ".join(base_color_scheme.keys())}', 38 | default=["white"], 39 | nargs="+", 40 | ) 41 | poster_create_parser.add_argument( 42 | '--output_prefix', 43 | help='Output file prefix. eq. "{OUTPUT_PREFIX}_{COLOR}.png". Default: "map"', 44 | type=str, 45 | default="map" 46 | ) 47 | 48 | return poster_create_parser 49 | 50 | 51 | def add_misc_shp_subparser(parent_parser) -> argparse.ArgumentParser: 52 | misc_parser = parent_parser.add_parser('shp', description='Shp download') 53 | return misc_parser 54 | 55 | 56 | def add_misc_geojson_subparser(parent_parser) -> argparse.ArgumentParser: 57 | misc_parser = parent_parser.add_parser('geojson', description='Create geoJSON') 58 | return misc_parser 59 | 60 | 61 | def add_poster_subparsers(parser_group) -> argparse.ArgumentParser: 62 | poster_commands_parser = parser_group.add_parser( 63 | 'poster', 64 | description='Create Map Poster', 65 | help='Poster creation', 66 | ) 67 | poster_commands_parser_group = poster_commands_parser.add_subparsers( 68 | title='poster management commands', 69 | description='Create poster', 70 | help='Additional help for available commands', 71 | dest='poster_commands', 72 | ) 73 | 74 | add_poster_create_subparser(poster_commands_parser_group) 75 | 76 | return poster_commands_parser 77 | 78 | 79 | def add_misc_subparsers(parser_group) -> argparse.ArgumentParser: 80 | misc_commands_parser = parser_group.add_parser( 81 | 'misc', 82 | description='Misc services', 83 | help='Misc services', 84 | ) 85 | misc_commands_parser_group = misc_commands_parser.add_subparsers( 86 | title='misc management commands', 87 | description='misc', 88 | help='Additional help for available commands', 89 | dest='misc_commands', 90 | ) 91 | 92 | add_misc_shp_subparser(misc_commands_parser_group) 93 | add_misc_geojson_subparser(misc_commands_parser_group) 94 | 95 | return misc_commands_parser 96 | 97 | 98 | def add_color_list_subparser(parent_parser) -> argparse.ArgumentParser: 99 | color_parser = parent_parser.add_parser('list', description="List available colors") 100 | return color_parser 101 | 102 | 103 | def add_color_add_subparser(parent_parser) -> argparse.ArgumentParser: 104 | color_parser = parent_parser.add_parser('add', description="List available colors") 105 | color_parser.add_argument('--name', help='Name of color scheme. eq. "blue"', required=True) 106 | color_parser.add_argument('--facecolor', help='MatPlot face hex color. eq. "#ffffff"', required=True) 107 | color_parser.add_argument('--water', help='MatPlot water hex color. eq. "#ffffff"', required=True) 108 | color_parser.add_argument('--greens', help='MatPlot greens hex color. eq. "#ffffff"', required=True) 109 | color_parser.add_argument('--roads', help='MatPlot roads hex color. eq. "#ffffff"', required=True) 110 | return color_parser 111 | 112 | 113 | def add_color_subparsers(parser_group) -> argparse.ArgumentParser: 114 | color_commands_parser = parser_group.add_parser( 115 | 'color', 116 | description='Color services', 117 | help='Color services', 118 | ) 119 | color_commands_parser_group = color_commands_parser.add_subparsers( 120 | title='color management commands', 121 | description='Color management', 122 | help='Additional help for available commands', 123 | dest='color_commands', 124 | ) 125 | 126 | add_color_add_subparser(color_commands_parser_group) 127 | add_color_list_subparser(color_commands_parser_group) 128 | 129 | return color_commands_parser 130 | 131 | 132 | def process_color_service_call(args: argparse.Namespace) -> None: 133 | command = args.color_commands 134 | if command == "list": 135 | pprint(get_color_schemes()) 136 | 137 | if command == "add": 138 | name = args.name 139 | facecolor = args.facecolor 140 | water = args.water 141 | greens = args.greens 142 | roads = args.roads 143 | 144 | add_user_color_scheme( 145 | name=name, 146 | facecolor=facecolor, 147 | water=water, 148 | greens=greens, 149 | roads=roads 150 | ) 151 | 152 | 153 | def process_misc_service_call(args: argparse.Namespace) -> None: 154 | command = args.misc_commands 155 | if command == 'shp': 156 | webbrowser.open_new_tab("https://download.geofabrik.de/") 157 | 158 | if command == "geojson": 159 | webbrowser.open_new_tab("https://geojson.io/") 160 | 161 | 162 | def process_poster_service_call(args: argparse.Namespace) -> None: 163 | command = args.poster_commands 164 | 165 | shp_path = args.shp_path 166 | geojson = args.geojson 167 | colors = args.colors 168 | output_prefix = args.output_prefix 169 | 170 | if command == 'create': 171 | create_poster( 172 | base_shp_path=shp_path, 173 | geojson_path=geojson, 174 | colors=colors, 175 | layers=[""], 176 | config={"none": None}, 177 | output_prefix=output_prefix, 178 | ) 179 | return 180 | 181 | 182 | def map_poster(argv=None) -> None: 183 | parser = get_root_parser() 184 | poster_creator_services_parser_group = parser.add_subparsers( 185 | title='Available Map Poster services', 186 | description='Services that Map Poster provides.', 187 | help='Additional help for available services', 188 | dest='map_poster_services', 189 | ) 190 | add_poster_subparsers(poster_creator_services_parser_group) 191 | add_misc_subparsers(poster_creator_services_parser_group) 192 | add_color_subparsers(poster_creator_services_parser_group) 193 | 194 | args = parser.parse_args(argv) 195 | 196 | service = args.map_poster_services 197 | available_services = { 198 | 'poster': process_poster_service_call, 199 | 'misc': process_misc_service_call, 200 | 'color': process_color_service_call, 201 | } 202 | if not service: 203 | parser.print_help() 204 | parser.print_usage() 205 | return 206 | available_services[service](args) 207 | 208 | 209 | if __name__ == '__main__': 210 | map_poster() 211 | --------------------------------------------------------------------------------