The OpenGS Map Tool is a specialized utility designed to streamline the creation of map data for use in grand strategy games. Province and territory maps form the backbone of these games, defining the geographical regions that players interact with.
- Generate and Export province maps
- Generate and Export province data
- Generate and Export territory maps
- Generate and Export territory data
- "Releases" section in Github
- Download and unpack "ogs_maptool.zip"
- Run the Executable
- Clone the repository
- Download the necessary libraries by running "pip install -r requirements.txt" in your terminal, inside the project directory
- Start project by running "python main.py"
The graphical interface provides an interactive, step-by-step workflow:
- Read the README and familiarize yourself with the workflow
- Import your boundary image (black lines on transparent background, RGB 0,0,0)
- Click "Normalize Territory and Province Density" to prepare it for density editing
- Use the blue channel to control region density:
- B=255 (white) → 4x more regions
- B=128 (light yellow) → normal density
- B=0 (dark yellow) → 4x fewer regions
- anything inbetween for nuanced control
- Keep or modify the generated boundary image
- Import your final boundary image
- Import and clean your land/ocean image
- Ocean must be RGB
(5, 20, 18) - Everything else is considered land
- Ocean must be RGB
- Both images must have the same dimensions
- Generates intermediate region divisions from your boundary image
- Adjust the RNG seed to try different layouts
- The same seed and input images will always generate the same output
- Creates larger regional divisions
- Adjust sliders for land and water territory sizes
- Change RNG seed for different randomization
- Export territory map and data (JSON/CSV) when satisfied
- Creates smaller subdivisions from territories
- Adjust sliders for land and water province sizes
- Change RNG seed for different randomization
- Export province map and data (JSON/CSV) when satisfied
Tips:
- Territories must be generated before provinces
- Use the "Normalize Density" feature for better control over region distribution
- Try different RNG seeds to explore different layouts
- All exports (maps and data files) are available after each generation step
For programmatic map generation, use the MapTool class directly:
from pathlib import Path
from PIL import Image
from logic.maptool import MapTool
import json
# Define input/output directories
input_dir = Path("examples/input")
output_dir = Path("examples/output")
# Create MapTool instance
maptool = MapTool(
land_image=Image.open(input_dir / "land2.png"),
boundary_image=Image.open(input_dir / "bound2_density.png"),
pixels_per_land_territory=6000, # Adjust territory size
pixels_per_water_territory=35000,
pixels_per_land_province=1200, # Adjust province size
pixels_per_water_province=7000,
lloyd_iterations=2, # Quality of Voronoi relaxation
cont_areas_rng_seed=int(1e6),
territories_rng_seed=int(2e6),
provinces_rng_seed=int(3e6),
)
# Generate all maps
result = maptool.generate()
# Save result images
result.cont_areas_image.save(output_dir / "cont_areas_image.png")
result.territory_image.save(output_dir / "territory_image.png")
result.province_image.save(output_dir / "province_image.png")
result.class_image.save(output_dir / "class_image.png")
# Save result data
output_data = {
"cont_areas": result.cont_areas_data,
"territories": result.territory_data,
"provinces": result.province_data,
"class_counts": result.class_counts,
}
(output_dir / "map_data.json").write_text(json.dumps(output_data, indent=2))Key Parameters:
pixels_per_land_territory/pixels_per_water_territory: Controls territory size (higher = fewer, larger territories)pixels_per_land_province/pixels_per_water_province: Controls province sizelloyd_iterations: Number of Lloyd relaxation iterations (1-10, higher = better spacing but slower)*_rng_seed: Random seeds for reproducible results (change to explore different layouts)
The generate() method returns a MapToolResult object containing both images and metadata:
result = maptool.generate()result.cont_areas_image- Colored continuous areas (if boundary provided)result.territory_image- Colored territory mapresult.province_image- Colored province mapresult.class_image- Cleaned land/ocean/lake classification
Each data field (cont_areas_data, territory_data, province_data) is a list of dictionaries representing regions. Each region dict contains:
| Field | Type | Description |
|---|---|---|
region_id |
str | int | Unique identifier (e.g., "area-001", "ter-001", "prv-042") |
region_type |
str | Type of region: "land", "ocean", or "lake" (determined by predominant pixel type) |
color |
str | Hex color code (e.g., "#a1b2c3") used in the map image |
parent_id |
str | int | None | ID of parent region (territory for provinces, area for territories, None for areas) |
density_multiplier |
float | Density factor applied (from boundary blue channel, only set for areas and inherited by territories) |
local_x, local_y |
float | Centroid coordinates: for territories/provinces = within parent(see parent_id) area; for areas = global |
global_x, global_y |
float | Centroid coordinates in the full map |
bbox_local |
list[int] | Bounding box: for territories/provinces = within parent area; for areas = global |
bbox |
list[int] | Bounding box in global coordinates |
Example region:
{
"region_type": "land",
"region_id": "ter-000235",
"parent_id": 113,
"color": "#b12a8c",
"local_x": 150.75,
"local_y": 96.39,
"global_x": 2892.75,
"global_y": 470.39,
"bbox_local": [119, 66, 193, 129],
"bbox": [2861, 440, 2935, 503],
"density_multiplier": 2.23
}result.class_counts is a dictionary showing pixel distribution:
{
"land": 450000, # Pixels classified as land
"ocean": 300000, # Pixels classified as ocean
"lake": 50000 # Pixels classified as lake
}When you export CSV/JSON from the GUI or via Python, this field data is included, allowing you to:
- Map region IDs to visual colors
- Calculate region areas (from bounding boxes)
- Track parent-child relationships (territory → provinces)
- Access precise centroid coordinates for game logic
The boundary image defines regions that provinces/territories must respect.
Boundaries must be pure black, RGB (0,0,0), everything else is ignored.
Examples:

Density Multiplier:
Use the blue channel to control region density in different areas:
After editing with density multipliers:

Best Practices:
- Divide large oceans and countries for better results
- Use density control to create varied region sizes (e.g., small provinces in densely populated areas, large ones in sparse regions)
Specifies ocean vs. land areas. Ocean pixels should be RGB (5, 20, 18) (dark teal).
Everything else is considered land.
Examples:

Note: You need at least one input image (boundary or land). Both are optional but must have the same dimensions if used together.
Contributions can come in many forms and all are appreciated:
- Feedback
- Code improvements
- Added functionality
Follow and/or support the project on OpenGS Discord Server