Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d0e8207
region map export using neither GDAL nor boost
chdoc Mar 30, 2025
d7a04d6
clean up and reinstate export of the remaining biome and region values
chdoc Apr 1, 2025
d899a2a
comments and cleanup
chdoc Apr 3, 2025
7043d2c
convert site export to CSV and remove GDAL dependency
chdoc Apr 3, 2025
3468e6f
use proper site classification and remove gdal include
chdoc Apr 3, 2025
5856c80
move sites classification to map module and additional cleanup
chdoc Apr 4, 2025
b8f92d8
Merge branch 'develop' into export-map-2
chdoc Apr 5, 2025
b53b44f
first version of river export in C++
chdoc May 4, 2025
f495b6d
Merge branch 'develop' into export-map-2
chdoc May 4, 2025
95eab7f
default to exporting all layers
chdoc May 4, 2025
4473f09
Merge branch 'develop' into export-map-2
chdoc Jul 11, 2026
9075ba4
add elevation export
chdoc Jul 14, 2026
721ca8e
Merge branch 'develop' into export-map-2
chdoc Aug 1, 2026
b6a3824
improve code quality
chdoc Aug 1, 2026
2f1bb5f
fix river confluence points
chdoc Aug 2, 2026
7c23828
more cleanup
chdoc Aug 2, 2026
394718a
refactor region output into multiple functions
chdoc Aug 2, 2026
70cb75e
documentation and changelog
chdoc Aug 2, 2026
9397f27
allow grouping of exports by world name and date
chdoc Aug 5, 2026
47d2c1c
Merge branch 'develop' into export-map-2
chdoc Aug 7, 2026
93a23af
parallelize processing of region tiles
chdoc Aug 8, 2026
bbebf84
Move functions to Maps module and update changelog
chdoc Aug 9, 2026
d08da22
fix changelog links
chdoc Aug 9, 2026
7da854b
Rename tool from `export-map` to `export-world-map`
chdoc Aug 9, 2026
b4bf4aa
fix export of Lua folder helpers
chdoc Aug 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ Template for new versions:
# Future

## New Tools
- `export-world-map`: Export world map data for GIS and other external tools.

## New Features

Expand All @@ -65,6 +66,10 @@ Template for new versions:
## Documentation

## API
- Added ``MiscUtils::print_range`` to print ranges with prefix, separator, and suffix
- Added ``Maps::getSiteTypeName`` to print the sites classification as a string (e.g. "mountain halls")
- Added ``Maps::addRegionBiomeOffset`` to shift world region coordinate by region_details biome reference
- Added ``Maps::describeSurroundings`` to get surroundings classification from savagery and evilness

## Lua

Expand Down
84 changes: 84 additions & 0 deletions docs/plugins/export-world-map.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
export-world-map
================

.. dfhack-tool::
:summary: Export world map data for GIS and other external tools.
:tags: inspection embark map

This plugin exports world-map information to files in the DFHack configuration
folder under the ``map-export`` directory. It must be run from the
embark-selection screen. The exports are generated at the granularity of
individual region tiles (16 x 16 per world tile). Those tiles are only generated
by DF as you scroll around on the zoomed-in embark map, which thus needs to be
done prior to running this export tool.

The supported export targets are:

- ``regions``: writes world tile regions and their biome information to ``regions.csv``.
- ``sites``: writes site extents and ownership/civilization information to ``sites.csv``.
- ``rivers``: writes rivers to ``rivers.csv``.
- ``elevation``: writes elevation data to ``elevation.dat`` and ``elevation.vrt``.

Unless you have software such as ArcGIS or QGIS/GDAL available, these exports
will be of little use.

Usage
-----

::

export-world-map
export-world-map <topic>
export-world-map <topic> <topic> ...
export-world-map all

If no topic is given, or if ``all`` is supplied, every supported export target
is run.

Options
-------

``--group-by-world``
Create a folder named after the world in the ``map-export`` directory.

``--group-by-date``
Create a folder with name ``year-month`` inside the world folder. Implies
``--group-by-world``.

Examples
--------

``export-world-map``
Export all supported datasets.

``export-world-map regions``
Export only region boundary geometries.

``export-world-map sites rivers``
Export site and river geometries only.


Notes on file formats
---------------------

To avoid depending on external libraries such as GDAL, the export formats are
kept as simple as possible to generate. This means raw bitmap data for the
elevation export and CSV files for the others. Before these files can be used
reasonably in software such as QGIS, they need to be assigned a coordinate
reference system (CRS) and converted into formats that can be accessed more
efficiently than CSV. While any CRS will do, WGS 84 / Pseudo-Mercator
(EPSG:3857) is the preferred choice.

For detailed map data (``regions`` and ``rivers``), Parquet is by far the
preferred format. The elevation export provides a VRT file to facilitate the
transformation into a GeoTIFF with the aforementioned CRS.

Assuming a recent version of GDAL built with Parquet support and with all
command-line tools in your PATH, this can be achieved with the following
commands:

::

ogr2ogr -a_srs "EPSG:3857" -of Parquet map.parquet -oo 'GEOM_POSSIBLE_NAMES=*wkt' -oo 'KEEP_GEOM_COLUMNS=NO' -oo 'AUTODETECT_TYPE=YES' map.csv
ogr2ogr -a_srs "EPSG:3857" -of Parquet rivers.parquet -oo 'GEOM_POSSIBLE_NAMES=*wkt' -oo 'KEEP_GEOM_COLUMNS=NO' -oo 'AUTODETECT_TYPE=YES' rivers.csv
gdal raster convert elevation.vrt elevation.tif
36 changes: 36 additions & 0 deletions library/include/MiscUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,42 @@ inline bool static_add_to_map(CT *pmap, const typename CT::key_type key, const t
/*
* MISC
*/
template<
std::ranges::input_range Range,
std::invocable<std::ostream&, std::ranges::range_reference_t<Range>> Callable>
void print_range(
std::ostream &out,
const Range &elements,
Callable&& print_element,
const std::string &prefix = "[",
const std::string &separator = ", ",
const std::string &suffix = "]"
){
out << prefix;
auto it = std::ranges::begin(elements);
auto end = std::ranges::end(elements);

if (it != end) {
print_element(out, *it);
for (++it; it != end; ++it) {
out << separator;
print_element(out, *it);
}
}
out << suffix;
}

template<std::ranges::input_range Range>
void print_range(
std::ostream &out,
const Range& elements,
const std::string &prefix = "[",
const std::string &separator = ", ",
const std::string &suffix = "]"
){
auto print_element = [](std::ostream &out, auto& e) { out << e; };
print_range(out, elements, print_element, prefix, separator, suffix);
}

DFHACK_EXPORT bool split_string(std::vector<std::string> *out,
const std::string &str, const std::string &separator,
Expand Down
2 changes: 1 addition & 1 deletion library/include/df/custom/coord2d.methods.inc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
coord2d(uint16_t _x, uint16_t _y) : x(_x), y(_y) {}
coord2d(int16_t _x, int16_t _y) : x(_x), y(_y) {}

bool isValid() const { return x >= 0; }
void clear() { x = y = -30000; }
Expand Down
13 changes: 13 additions & 0 deletions library/include/modules/Maps.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ distribution.
#include "df/matter_state.h"
#include "df/tile_dig_designation.h"
#include "df/tiletype.h"
#include "df/world_site.h"

namespace df {
struct block_square_event;
Expand Down Expand Up @@ -351,6 +352,8 @@ inline df::tiletype *getTileType(df::coord pos) { return getTileType(pos.x, pos.
inline df::tile_designation *getTileDesignation(df::coord pos) { return getTileDesignation(pos.x, pos.y, pos.z); }
inline df::tile_occupancy *getTileOccupancy(df::coord pos) { return getTileOccupancy(pos.x, pos.y, pos.z); }

// shift world region coordinate by region_details biome reference
DFHACK_EXPORT df::coord2d addRegionBiomeOffset(df::coord2d world_pos, int8_t offset_dir);
// Returns biome info about the specified world region.
DFHACK_EXPORT df::region_map_entry *getRegionBiome(df::coord2d rgn_pos);

Expand Down Expand Up @@ -411,6 +414,16 @@ DFHACK_EXPORT int removeAreaAquifer(df::coord pos1, df::coord pos2,
std::function<bool(df::coord, df::map_block *)> filter = [](df::coord pos, df::map_block *block) { return true; });

DFHACK_EXPORT void addBlockColumns(int32_t new_height);

// Get surroundings classification from savagery and evilness
DFHACK_EXPORT const char* describeSurroundings(int savagery, int evilness);

/**
* A single function does not merit a "Sites" module, hence we collect site functions here in the meantime.
*/

// Get the classification string (e.g. "town", "hillocs", "tower", etc.) for a site
DFHACK_EXPORT const char* getSiteTypeName(df::world_site *site);
}
}
#endif
126 changes: 120 additions & 6 deletions library/modules/Maps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,15 @@ distribution.
#include "modules/Maps.h"

#include "df/biome_type.h"
#include "df/block_burrow.h"
#include "df/block_burrow_link.h"
#include "df/block_burrow.h"
#include "df/block_column_print_infost.h"
#include "df/block_square_event_grassst.h"
#include "df/block_square_event_item_spatterst.h"
#include "df/block_square_event_material_spatterst.h"
#include "df/block_square_event_spoorst.h"
#include "df/building.h"
#include "df/building_type.h"
#include "df/building.h"
#include "df/builtin_mats.h"
#include "df/burrow.h"
#include "df/entity_plot_invasion_mapst.h"
Expand All @@ -56,22 +56,25 @@ distribution.
#include "df/flow_info.h"
#include "df/historical_entity.h"
#include "df/invasion_info.h"
#include "df/map_block.h"
#include "df/map_block_column.h"
#include "df/map_block.h"
#include "df/material.h"
#include "df/plant.h"
#include "df/plant_root_tile.h"
#include "df/plant_tree_info.h"
#include "df/plant_tree_tile.h"
#include "df/plotinfost.h"
#include "df/plant.h"
#include "df/plot_invasion_mapst.h"
#include "df/plotinfost.h"
#include "df/region_map_entry.h"
#include "df/world.h"
#include "df/site_map_infost.h"
#include "df/world_data.h"
#include "df/world_geo_biome.h"
#include "df/world_geo_layer.h"
#include "df/world_region_details.h"
#include "df/world_site_type.h"
#include "df/world_site.h"
#include "df/world_underground_region.h"
#include "df/world.h"
#include "df/z_level_flags.h"


Expand Down Expand Up @@ -433,6 +436,22 @@ df::tile_occupancy *Maps::getTileOccupancy(int32_t x, int32_t y, int32_t z)
return block ? &block->occupancy[x&15][y&15] : NULL;
}

df::coord2d Maps::addRegionBiomeOffset(df::coord2d world_pos, int8_t offset_dir) {
// Note 1: textual order of offfsets is upside down compared to the keypad order used by the biome offset
// Note 2: also upside down compared to (fort) map block region offsets
constexpr auto biome_offset = std::to_array<std::pair<int16_t, int16_t>>({
{-1, 1}, {0, 1}, {1, 1},
{-1, 0}, {0, 0}, {1, 0},
{-1,-1}, {0,-1}, {1,-1}
});

auto [diff_x, diff_y] = biome_offset[std::clamp(offset_dir, (int8_t)1, (int8_t)9) - 1];
return {
(int16_t)std::clamp(world_pos.x + diff_x,0,world->world_data->world_width - 1),
(int16_t)std::clamp(world_pos.y + diff_y,0,world->world_data->world_height - 1)
};
}

df::region_map_entry *Maps::getRegionBiome(df::coord2d rgn_pos)
{
auto data = world->world_data;
Expand Down Expand Up @@ -1532,6 +1551,17 @@ int Maps::removeAreaAquifer(df::coord pos1, df::coord pos2, std::function<bool(d
return totalAffectedCount;
}

const char* Maps::describeSurroundings(int savagery, int evilness) {
constexpr std::array<const char*,9>surroundings{
"Serene", "Mirthful", "Joyous Wilds",
"Calm", "Wilderness", "Untamed Wilds",
"Sinister", "Haunted", "Terrifying"
};
auto savagery_index = savagery < 33 ? 0 : (savagery > 65 ? 2 : 1);
auto evilness_index = evilness < 33 ? 0 : (evilness > 65 ? 2 : 1);
return surroundings[3 * evilness_index + savagery_index];
}

void Maps::addBlockColumns(int32_t new_height)
{
auto quantity = new_height - world->map.z_count_block;
Expand Down Expand Up @@ -1652,3 +1682,87 @@ void Maps::addBlockColumns(int32_t new_height)
}
}
}



// reverse engineered from DF 50.13 (FUN_140d82ca0, likely sitest::get_site_type_name)
const char* Maps::getSiteTypeName(df::world_site *site) {
using wst = df::enums::world_site_type::world_site_type;
switch (site->type) {
case wst::PlayerFortress:
case wst::MountainHalls:
if (site->min_depth == 0 && (0 < site->max_depth)){
return "fortress";
}
if (site->min_depth > 0) {
return "mountain halls";
}
return "hillocks";

case wst::DarkFortress: {
bool has_market = site->flag.is_set(df::enums::site_flag_type::HAS_MARKET);
return has_market ? "fortress" : "pits";
}

case wst::Cave:
return "cave";

case wst::ForestRetreat:
return "forest retreat";

case wst::Town: {
bool has_market = site->flag.is_set(df::enums::site_flag_type::HAS_MARKET);
return has_market ? "town" : "hamlet";
}

case wst::ImportantLocation:
return "important location";

case wst::LairShrine:
if (site->subtype_info) {
switch (site->subtype_info->lair_type) {
case df::enums::lair_type::LABYRINTH:
return "labyrinth";
case df::enums::lair_type::SHRINE:
return "shrine";
default:
break;
}
}
return "lair";

case wst::Fortress:
if (site->subtype_info) {
switch (site->subtype_info->fortress_type) {
case df::enums::fortress_type::TOWER:
return "tower";
case df::enums::fortress_type::MONASTERY:
return "monastery";
case df::enums::fortress_type::FORT:
return "fort";
default:
return "castle";
}
}
return "fortress";

case wst::Camp:
return "camp";

case wst::Monument:
if (site->subtype_info) {
switch (site->subtype_info->monument_type) {
case df::enums::monument_type::TOMB:
return "tomb";
case df::enums::monument_type::VAULT:
return "vault";
default:
break;
}
}
return "monument";

default:
return "site";
}
}
1 change: 1 addition & 0 deletions plugins/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ if(BUILD_SUPPORTED)
#add_subdirectory(embark-assistant)
dfhack_plugin(edgescroll edgescroll.cpp)
dfhack_plugin(eventful eventful.cpp LINK_LIBRARIES lua)
dfhack_plugin(export-world-map export-world-map.cpp LINK_LIBRARIES lua)
dfhack_plugin(fastdwarf fastdwarf.cpp)
dfhack_plugin(filltraffic filltraffic.cpp)
dfhack_plugin(fix-occupancy fix-occupancy.cpp LINK_LIBRARIES lua)
Expand Down
Loading
Loading