Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 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-map`: Export world map data for GIS and other external tools.

## New Features

Expand Down
74 changes: 74 additions & 0 deletions docs/plugins/export-map.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
export-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 ``map.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-map
export-map <topic>
export-map <topic> <topic> ...
export-map all

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

Examples
--------

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

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

``export-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
9 changes: 9 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 @@ -411,6 +412,14 @@ 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);


/**
* 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
99 changes: 93 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 @@ -1652,3 +1655,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-map export-map.cpp COMPILE_FLAGS_GCC -fno-gnu-unique)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is requiring -fno-gnu-unique?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably a STL template; it can be hard to figure out which one because you have to pore over the libc++ implementation and that's very timeconsuming.

-fno-gnu-unique should always be safe for DFHack plugins, as long as they don't crossload another another plug-in module

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing is actually requiring this. Is what a (failed) attempt to make the plugin unload. I will have to check whether it unloads, now that I have removed the GDAL dependency. Keeping this there and the conversation open in the meantime.

dfhack_plugin(fastdwarf fastdwarf.cpp)
dfhack_plugin(filltraffic filltraffic.cpp)
dfhack_plugin(fix-occupancy fix-occupancy.cpp LINK_LIBRARIES lua)
Expand Down
Loading
Loading