From bf33e08efc0e20c9d95da7b9ab2a053f8e3efad1 Mon Sep 17 00:00:00 2001 From: Christian Doczkal <20443222+chdoc@users.noreply.github.com> Date: Sun, 23 Feb 2025 20:19:49 +0100 Subject: [PATCH 1/4] map export to geoparquet using GDAL (prototype) --- plugins/CMakeLists.txt | 1 + plugins/export-map.cpp | 211 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 212 insertions(+) create mode 100644 plugins/export-map.cpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 8f230ff583..f87f9002ce 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -71,6 +71,7 @@ if(BUILD_SUPPORTED) #dfhack_plugin(dwarfmonitor dwarfmonitor.cpp LINK_LIBRARIES lua) #add_subdirectory(embark-assistant) dfhack_plugin(eventful eventful.cpp LINK_LIBRARIES lua) + dfhack_plugin(export-map export-map.cpp COMPILE_FLAGS_GCC -fno-gnu-unique LINK_LIBRARIES gdal) dfhack_plugin(fastdwarf fastdwarf.cpp) dfhack_plugin(filltraffic filltraffic.cpp) dfhack_plugin(fix-occupancy fix-occupancy.cpp LINK_LIBRARIES lua) diff --git a/plugins/export-map.cpp b/plugins/export-map.cpp new file mode 100644 index 0000000000..0a9336a259 --- /dev/null +++ b/plugins/export-map.cpp @@ -0,0 +1,211 @@ +#include "Debug.h" +#include "Error.h" +#include "PluginManager.h" +#include "MiscUtils.h" + +#include "modules/Maps.h" +#include "modules/Translation.h" + +#include "df/world.h" +#include "df/map_block.h" +#include "df/world_data.h" +#include "df/region_map_entry.h" +#include "df/world_region.h" +#include "df/world_region_details.h" + +#include "gdal/ogrsf_frmts.h" + +#include +#include + +using std::string; +using std::vector; + +using namespace DFHack; + +DFHACK_PLUGIN("export-map"); + +REQUIRE_GLOBAL(world); + +namespace DFHack { + DBG_DECLARE(exportmap, log); +} + +static command_result do_command(color_ostream &out, vector ¶meters); + +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { + DEBUG(log,out).print("initializing %s\n", plugin_name); + + commands.push_back(PluginCommand( + plugin_name, + "Export the world map.", + do_command)); + + return CR_OK; +} + +auto setGeometry(OGRFeature *feature, double x, double y, double dim) { + auto poly = new OGRPolygon(); + auto boundary = new OGRLinearRing(); + y = -y; + boundary->addPoint(x,y); + boundary->addPoint(x,y-dim); + boundary->addPoint(x+dim,y-dim); + boundary->addPoint(x+dim,y); + boundary->closeRings(); + //the "Directly" variants assume ownership of the objects created above + poly->addRingDirectly(boundary); + feature->SetGeometryDirectly( poly ); +} + +auto get_world_index(int world_x, int world_y, int8_t dir) { + switch (dir) { + case 1: world_x-- ; world_y++; break; + case 2: ; world_y++; break; + case 3: world_x++ ; world_y++; break; + case 4: world_x-- ; ; break; + // case 5 induces no change + case 6: world_x++ ; ; break; + case 7: world_x-- ; world_y--; break; + case 8: ; world_y--; break; + case 9: world_x++ ; world_y--; break; + } + world_x = std::min(std::max(0,world_x),world->world_data->world_width - 1); + world_y = std::min(std::max(0,world_y),world->world_data->world_height - 1); + return std::pair(world_x,world_y); +} + +auto create_field(OGRLayer *layer, std::string name, OGRFieldType type, int width = 0, OGRFieldSubType subtype = OFSTNone) { + OGRFieldDefn field( name.c_str() , type ); + if (subtype != OFSTNone) { + field.SetSubType(subtype); + } + if (width != 0) { + field.SetWidth(width); + } + // this should create a copy internally + if( layer->CreateField( &field ) != OGRERR_NONE ){ + throw CR_FAILURE; + } +} + +// PROJ.4 description of EPSG:3857 (https://epsg.io/3857) +static const char* EPSG_3857 = "+proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 +y_0=0 +k=1 +units=m +nadgrids=@null +wktext +no_defs +type=crs"; + +static command_result do_command(color_ostream &out, vector ¶meters) { + out.print("exporting map... "); + + // set up coordinate system + OGRSpatialReference CRS; + if (CRS.importFromProj4(EPSG_3857) != OGRERR_NONE) { + out.printerr("could not set up coordinate system"); + return CR_FAILURE; + } + + // set up output driver + GDALAllRegister(); + const char *driver_name = "Parquet"; + const char *extension = "parquet"; + // const char *driver_name = "GPKG"; + // const char *extension = "gpkg"; + auto driver = GetGDALDriverManager()->GetDriverByName(driver_name); + CHECK_NULL_POINTER(driver); + + // create a data set and associate it to a file + std::string map("map."); + map.append(extension); + auto dataset = driver->Create( map.c_str(), 0, 0, 0, GDT_Unknown, NULL ); + + // create a layer for the biome data + auto layer = dataset->CreateLayer( "world_biomes", &CRS, wkbPolygon, NULL ); + + try { + create_field(layer, "biome_type", OFTString, 32); + create_field(layer, "elevation", OFTInteger); + create_field(layer, "evilness", OFTInteger); + create_field(layer, "savagery", OFTInteger); + create_field(layer, "region_id", OFTInteger); + create_field(layer, "region_name_en", OFTString, 100); + create_field(layer, "region_name_df", OFTString, 100); + create_field(layer, "landmass_id", OFTInteger); + create_field(layer, "volcanism", OFTInteger); + create_field(layer, "drainage", OFTInteger); + }catch (const DFHack::command_result& r) { + out.printerr("could not create fields for output layer"); + return r; + } + + #define REGION 1 + #ifndef REGION + for (int x = 0; x < world->world_data->world_width; ++x) { + for (int y = 0; y < world->world_data->world_height; ++y) { + // auto& entry = world->world_data->region_map[x][y]; + + auto feature = OGRFeature::CreateFeature( layer->GetLayerDefn() ); + setGeometry(feature, x, y, 1.0); + + auto biome = ENUM_KEY_STR(biome_type, Maps::getBiomeType(x,y)); + feature->SetField( "biome_type", biome.c_str() ); + + // updates the feature with the id it receives in the layer + if( layer->CreateFeature( feature ) != OGRERR_NONE ) + return CR_FAILURE; + + OGRFeature::DestroyFeature( feature ); + } + }Maps::getBiomeType(world_x + x_offset,world_y + y_offset)); + #else + int wdim = 768; // dimension of a world tile + int rdim = 48; // dimension of a region tile + for (auto const region_details : world->world_data->midmap_data.region_details) { + auto world_x = region_details->pos.x; + auto world_y = region_details->pos.y; + for (int region_x = 0; region_x < 16; ++region_x) { + for (int region_y = 0; region_y < 16; ++region_y) { + + auto feature = OGRFeature::CreateFeature( layer->GetLayerDefn() ); + setGeometry( + feature, + (double)(world_x * wdim + region_x * rdim), + (double)(world_y * wdim + region_y * rdim), + rdim + ); + + // get information from the region details + auto [biome_x,biome_y] = get_world_index(world_x, world_y, region_details->biome[region_x][region_y]); + feature->SetField( "biome_type", ENUM_KEY_STR(biome_type,Maps::getBiomeType(biome_x,biome_y)).c_str() ); + feature->SetField( "elevation", region_details->elevation[region_x][region_y]); + + // gets supplementary information from the world tile + auto& world_entry = world->world_data->region_map[biome_x][biome_y]; + #define SET_FIELD(name) feature->SetField( #name, world_entry.name) + SET_FIELD(evilness); + SET_FIELD(savagery); + SET_FIELD(region_id); + SET_FIELD(landmass_id); + SET_FIELD(volcanism); + SET_FIELD(drainage); + #undef SET_FIELD + + auto region = df::world_region::find(world_entry.region_id); + if (region) { + auto region_name_en = DF2UTF(Translation::translateName(®ion->name, true)); + feature->SetField( "region_name_en", region_name_en.c_str()); + auto region_name_df = DF2UTF(Translation::translateName(®ion->name, false)); + feature->SetField( "region_name_df", region_name_df.c_str()); + } + + // this updates the feature with the id it receives in the layer + if( layer->CreateFeature( feature ) != OGRERR_NONE ) + return CR_FAILURE; + // but we don't care and destroy the feature + OGRFeature::DestroyFeature( feature ); + } + } + } + #endif + + GDALClose( dataset ); + out.print("done!\n"); + return CR_OK; +} From 539652a97919bcd956aa0279ff6e1f7c20801b6e Mon Sep 17 00:00:00 2001 From: Christian Doczkal <20443222+chdoc@users.noreply.github.com> Date: Sat, 1 Mar 2025 16:14:37 +0100 Subject: [PATCH 2/4] more fields and execution timing --- plugins/export-map.cpp | 77 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 69 insertions(+), 8 deletions(-) diff --git a/plugins/export-map.cpp b/plugins/export-map.cpp index 0a9336a259..79f2f233c1 100644 --- a/plugins/export-map.cpp +++ b/plugins/export-map.cpp @@ -11,12 +11,14 @@ #include "df/world_data.h" #include "df/region_map_entry.h" #include "df/world_region.h" +#include "df/world_landmass.h" #include "df/world_region_details.h" #include "gdal/ogrsf_frmts.h" #include #include +#include using std::string; using std::vector; @@ -47,7 +49,7 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector addPoint(x,y); boundary->addPoint(x,y-dim); boundary->addPoint(x+dim,y-dim); @@ -92,8 +94,35 @@ auto create_field(OGRLayer *layer, std::string name, OGRFieldType type, int widt // PROJ.4 description of EPSG:3857 (https://epsg.io/3857) static const char* EPSG_3857 = "+proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 +y_0=0 +k=1 +units=m +nadgrids=@null +wktext +no_defs +type=crs"; + + +const char* describe_surroundings(int savagery, int evilness) { + constexpr std::arraysurroundings{ + "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]; +} + static command_result do_command(color_ostream &out, vector ¶meters) { + // we don't want DF to delete region details while we are iterating over them... + CoreSuspender suspend; + + if (!Core::getInstance().isWorldLoaded()){ + out.printerr("This command requires a world to be loaded\n"); + return CR_WRONG_USAGE; + } + + out.print("%lu out of %d region map tiles loaded\n", + world->world_data->midmap_data.region_details.size(), + world->world_data->world_width * world->world_data->world_height + ); out.print("exporting map... "); + out.flush(); + const auto start{std::chrono::steady_clock::now()}; // set up coordinate system OGRSpatialReference CRS; @@ -120,16 +149,30 @@ static command_result do_command(color_ostream &out, vector ¶meters) auto layer = dataset->CreateLayer( "world_biomes", &CRS, wkbPolygon, NULL ); try { - create_field(layer, "biome_type", OFTString, 32); - create_field(layer, "elevation", OFTInteger); - create_field(layer, "evilness", OFTInteger); - create_field(layer, "savagery", OFTInteger); create_field(layer, "region_id", OFTInteger); create_field(layer, "region_name_en", OFTString, 100); create_field(layer, "region_name_df", OFTString, 100); create_field(layer, "landmass_id", OFTInteger); + create_field(layer, "landmass_name_en", OFTString, 100); + create_field(layer, "landmass_name_df", OFTString, 100); + + create_field(layer, "biome_type", OFTString, 32); + create_field(layer, "surroundings", OFTString, 16); + create_field(layer, "elevation", OFTInteger); + + create_field(layer, "evilness", OFTInteger); + create_field(layer, "savagery", OFTInteger); create_field(layer, "volcanism", OFTInteger); create_field(layer, "drainage", OFTInteger); + create_field(layer, "temperature", OFTInteger); + create_field(layer, "vegetation", OFTInteger); + create_field(layer, "rainfall", OFTInteger); + create_field(layer, "snowfall", OFTInteger); + create_field(layer, "salinity", OFTInteger); + + create_field(layer, "reanimating", OFTInteger, OFSTBoolean); + create_field(layer, "has_bogeymen", OFTInteger, OFSTBoolean); + }catch (const DFHack::command_result& r) { out.printerr("could not create fields for output layer"); return r; @@ -179,20 +222,36 @@ static command_result do_command(color_ostream &out, vector ¶meters) // gets supplementary information from the world tile auto& world_entry = world->world_data->region_map[biome_x][biome_y]; #define SET_FIELD(name) feature->SetField( #name, world_entry.name) - SET_FIELD(evilness); - SET_FIELD(savagery); SET_FIELD(region_id); SET_FIELD(landmass_id); + SET_FIELD(evilness); + SET_FIELD(savagery); SET_FIELD(volcanism); SET_FIELD(drainage); + SET_FIELD(temperature); + SET_FIELD(vegetation); + SET_FIELD(rainfall); + SET_FIELD(snowfall); + SET_FIELD(salinity); #undef SET_FIELD + feature->SetField( "surroundings", describe_surroundings(world_entry.savagery, world_entry.evilness)); + auto region = df::world_region::find(world_entry.region_id); if (region) { auto region_name_en = DF2UTF(Translation::translateName(®ion->name, true)); feature->SetField( "region_name_en", region_name_en.c_str()); auto region_name_df = DF2UTF(Translation::translateName(®ion->name, false)); feature->SetField( "region_name_df", region_name_df.c_str()); + feature->SetField("reanimating", region->reanimating); + feature->SetField("has_bogeymen", region->has_bogeymen); + } + auto landmass = df::world_landmass::find(world_entry.landmass_id); + if (landmass) { + auto landmass_name_en = DF2UTF(Translation::translateName(&landmass->name, true)); + feature->SetField( "landmass_name_en", landmass_name_en.c_str()); + auto landmass_name_df = DF2UTF(Translation::translateName(&landmass->name, false)); + feature->SetField( "landmass_name_df", landmass_name_df.c_str()); } // this updates the feature with the id it receives in the layer @@ -206,6 +265,8 @@ static command_result do_command(color_ostream &out, vector ¶meters) #endif GDALClose( dataset ); - out.print("done!\n"); + const auto finish{std::chrono::steady_clock::now()}; + const std::chrono::duration elapsed_seconds{finish - start}; + out.print("done in %f ms !\n", elapsed_seconds.count()); return CR_OK; } From 4eded3458abcf2d8255141a19f33943a93c1ecd7 Mon Sep 17 00:00:00 2001 From: Christian Doczkal <20443222+chdoc@users.noreply.github.com> Date: Sat, 1 Mar 2025 19:01:07 +0100 Subject: [PATCH 3/4] cleanup --- plugins/export-map.cpp | 44 +++++++++++------------------------------- 1 file changed, 11 insertions(+), 33 deletions(-) diff --git a/plugins/export-map.cpp b/plugins/export-map.cpp index 79f2f233c1..7e6732369d 100644 --- a/plugins/export-map.cpp +++ b/plugins/export-map.cpp @@ -94,8 +94,6 @@ auto create_field(OGRLayer *layer, std::string name, OGRFieldType type, int widt // PROJ.4 description of EPSG:3857 (https://epsg.io/3857) static const char* EPSG_3857 = "+proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 +y_0=0 +k=1 +units=m +nadgrids=@null +wktext +no_defs +type=crs"; - - const char* describe_surroundings(int savagery, int evilness) { constexpr std::arraysurroundings{ "Serene", "Mirthful", "Joyous Wilds", @@ -116,7 +114,7 @@ static command_result do_command(color_ostream &out, vector ¶meters) return CR_WRONG_USAGE; } - out.print("%lu out of %d region map tiles loaded\n", + out.print("%lu / %d region map tiles loaded\n", world->world_data->midmap_data.region_details.size(), world->world_data->world_width * world->world_data->world_height ); @@ -135,8 +133,6 @@ static command_result do_command(color_ostream &out, vector ¶meters) GDALAllRegister(); const char *driver_name = "Parquet"; const char *extension = "parquet"; - // const char *driver_name = "GPKG"; - // const char *extension = "gpkg"; auto driver = GetGDALDriverManager()->GetDriverByName(driver_name); CHECK_NULL_POINTER(driver); @@ -170,36 +166,19 @@ static command_result do_command(color_ostream &out, vector ¶meters) create_field(layer, "snowfall", OFTInteger); create_field(layer, "salinity", OFTInteger); - create_field(layer, "reanimating", OFTInteger, OFSTBoolean); - create_field(layer, "has_bogeymen", OFTInteger, OFSTBoolean); + create_field(layer, "reanimating", OFTInteger, 0, OFSTBoolean); + create_field(layer, "has_bogeymen", OFTInteger, 0, OFSTBoolean); }catch (const DFHack::command_result& r) { out.printerr("could not create fields for output layer"); return r; } - #define REGION 1 - #ifndef REGION - for (int x = 0; x < world->world_data->world_width; ++x) { - for (int y = 0; y < world->world_data->world_height; ++y) { - // auto& entry = world->world_data->region_map[x][y]; - - auto feature = OGRFeature::CreateFeature( layer->GetLayerDefn() ); - setGeometry(feature, x, y, 1.0); - - auto biome = ENUM_KEY_STR(biome_type, Maps::getBiomeType(x,y)); - feature->SetField( "biome_type", biome.c_str() ); - - // updates the feature with the id it receives in the layer - if( layer->CreateFeature( feature ) != OGRERR_NONE ) - return CR_FAILURE; - - OGRFeature::DestroyFeature( feature ); - } - }Maps::getBiomeType(world_x + x_offset,world_y + y_offset)); - #else int wdim = 768; // dimension of a world tile int rdim = 48; // dimension of a region tile + + // iterating over the region details allows the user to do partial map exports + // by manually scrolling on the embark site selection for (auto const region_details : world->world_data->midmap_data.region_details) { auto world_x = region_details->pos.x; auto world_y = region_details->pos.y; @@ -220,8 +199,8 @@ static command_result do_command(color_ostream &out, vector ¶meters) feature->SetField( "elevation", region_details->elevation[region_x][region_y]); // gets supplementary information from the world tile - auto& world_entry = world->world_data->region_map[biome_x][biome_y]; - #define SET_FIELD(name) feature->SetField( #name, world_entry.name) + auto& region_map_entry = world->world_data->region_map[biome_x][biome_y]; + #define SET_FIELD(name) feature->SetField( #name, region_map_entry.name) SET_FIELD(region_id); SET_FIELD(landmass_id); SET_FIELD(evilness); @@ -235,9 +214,9 @@ static command_result do_command(color_ostream &out, vector ¶meters) SET_FIELD(salinity); #undef SET_FIELD - feature->SetField( "surroundings", describe_surroundings(world_entry.savagery, world_entry.evilness)); + feature->SetField( "surroundings", describe_surroundings(region_map_entry.savagery, region_map_entry.evilness)); - auto region = df::world_region::find(world_entry.region_id); + auto region = df::world_region::find(region_map_entry.region_id); if (region) { auto region_name_en = DF2UTF(Translation::translateName(®ion->name, true)); feature->SetField( "region_name_en", region_name_en.c_str()); @@ -246,7 +225,7 @@ static command_result do_command(color_ostream &out, vector ¶meters) feature->SetField("reanimating", region->reanimating); feature->SetField("has_bogeymen", region->has_bogeymen); } - auto landmass = df::world_landmass::find(world_entry.landmass_id); + auto landmass = df::world_landmass::find(region_map_entry.landmass_id); if (landmass) { auto landmass_name_en = DF2UTF(Translation::translateName(&landmass->name, true)); feature->SetField( "landmass_name_en", landmass_name_en.c_str()); @@ -262,7 +241,6 @@ static command_result do_command(color_ostream &out, vector ¶meters) } } } - #endif GDALClose( dataset ); const auto finish{std::chrono::steady_clock::now()}; From 87492a69b302b9825403f01ab96fd75e01e096f8 Mon Sep 17 00:00:00 2001 From: Christian Doczkal <20443222+chdoc@users.noreply.github.com> Date: Thu, 13 Mar 2025 18:30:37 +0100 Subject: [PATCH 4/4] export site data into a spatialite database --- plugins/export-map.cpp | 170 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 160 insertions(+), 10 deletions(-) diff --git a/plugins/export-map.cpp b/plugins/export-map.cpp index 7e6732369d..0891cd5701 100644 --- a/plugins/export-map.cpp +++ b/plugins/export-map.cpp @@ -6,9 +6,13 @@ #include "modules/Maps.h" #include "modules/Translation.h" +#include "df/entity_raw.h" +#include "df/creature_raw.h" +#include "df/historical_entity.h" #include "df/world.h" #include "df/map_block.h" #include "df/world_data.h" +#include "df/world_site.h" #include "df/region_map_entry.h" #include "df/world_region.h" #include "df/world_landmass.h" @@ -46,14 +50,15 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector addPoint(x,y); - boundary->addPoint(x,y-dim); - boundary->addPoint(x+dim,y-dim); - boundary->addPoint(x+dim,y); + boundary->addPoint(x,y-dimy); + boundary->addPoint(x+dimx,y-dimy); + boundary->addPoint(x+dimx,y); boundary->closeRings(); //the "Directly" variants assume ownership of the objects created above poly->addRingDirectly(boundary); @@ -105,8 +110,14 @@ const char* describe_surroundings(int savagery, int evilness) { return surroundings[3 * evilness_index + savagery_index]; } -static command_result do_command(color_ostream &out, vector ¶meters) { - // we don't want DF to delete region details while we are iterating over them... +static int wdim = 768; // dimension of a world tile +static int rdim = 48; // dimension of a region tile + +static command_result export_region_tiles(color_ostream &out); +static command_result export_sites(color_ostream &out); + +static command_result do_command(color_ostream &out, vector ¶meters) +{ CoreSuspender suspend; if (!Core::getInstance().isWorldLoaded()){ @@ -114,6 +125,148 @@ static command_result do_command(color_ostream &out, vector ¶meters) return CR_WRONG_USAGE; } + if (parameters.size() && parameters[0] == "sites") + { + return export_sites(out); + } + else + { + return export_region_tiles(out); + } +} + +static command_result export_sites(color_ostream &out) +{ + out.print("exporting sites... "); + out.flush(); + const auto start{std::chrono::steady_clock::now()}; + + // set up coordinate system + OGRSpatialReference CRS; + if (CRS.importFromProj4(EPSG_3857) != OGRERR_NONE) { + out.printerr("could not set up coordinate system"); + return CR_FAILURE; + } + + // set up output driver + GDALAllRegister(); + const char *driver_name = "SQLite"; + const char *extension = "sqlite"; + auto driver = GetGDALDriverManager()->GetDriverByName(driver_name); + if (!driver) { + out.printerr("could not find sqlite driver"); + return CR_FAILURE; + } + + // create a dataset and associate it to a file + std::string sites("sites."); + sites.append(extension); + const char* options[] = { "SPATIALITE=YES", nullptr }; + auto dataset = driver->Create( sites.c_str(), 0, 0, 0, GDT_Unknown, options); + if (!dataset) { + out.printerr("could not create dataset"); + return CR_FAILURE; + } + + // create a layer for the biome data + // const char* format[] = { "FORMAT=WKT", nullptr }; + auto layer = dataset->CreateLayer( "world_sites", &CRS, wkbPolygon, nullptr ); + if (!layer) { + out.printerr("could not create layer"); + return CR_FAILURE; + } + + try { + create_field(layer, "site_id", OFTInteger); + create_field(layer, "civ_id", OFTInteger); + create_field(layer, "created_year", OFTInteger); + create_field(layer, "cur_owner_id", OFTInteger); + + create_field(layer, "type", OFTString, 15); + + create_field(layer, "site_name_df", OFTString, 100); + create_field(layer, "site_name_en", OFTString, 100); + + create_field(layer, "civ_name_df", OFTString, 100); + create_field(layer, "civ_name_en", OFTString, 100); + + create_field(layer, "site_government_df", OFTString, 100); + create_field(layer, "site_government_en", OFTString, 100); + + create_field(layer, "owner_race", OFTString, 15); + + // create_field(layer, "local_ruler", OFTString, 100); + + } + catch (const DFHack::command_result& r) { + out.printerr("could not create fields for output layer"); + return r; + } + + if (dataset->StartTransaction() != OGRERR_NONE) { + out.printerr("could not start a transaction\n"); + } + + for (auto const site : world->world_data->sites) + { + + auto feature = OGRFeature::CreateFeature( layer->GetLayerDefn() ); + + setGeometry( + feature, + site->global_min_x * rdim, + site->global_min_y * rdim, + (site->global_max_x - site->global_min_x + 1) * rdim, + (site->global_max_y - site->global_min_y + 1) * rdim + ); + feature->SetField( "site_id", site->id ); + feature->SetField( "type", ENUM_KEY_STR(world_site_type, site->type).c_str() ); + #define SET_FIELD(name) feature->SetField( #name, site->name) + SET_FIELD(civ_id); + SET_FIELD(created_year); + SET_FIELD(cur_owner_id); + #undef SET_FIELD + + #define TRANSLATE_NAME(field_name, name_object)\ + feature->SetField((#field_name"_df"), DF2UTF(Translation::translateName(&name_object, false)).c_str());\ + feature->SetField((#field_name"_en"), DF2UTF(Translation::translateName(&name_object, true)).c_str()); + + TRANSLATE_NAME(site_name, site->name) + + auto civ = df::historical_entity::find(site->civ_id); + if (civ) { TRANSLATE_NAME(civ_name,civ->name) } + + auto owner = df::historical_entity::find(site->cur_owner_id); + if (owner) { + TRANSLATE_NAME(site_government,owner->name) + auto race = df::creature_raw::find(owner->race); + if (!race){ + race = df::creature_raw::find(civ->race); + } + if (race) { + feature->SetField( "owner_race", race->name[2].c_str() ); + } + } + + + // this updates the feature with the id it receives in the layer + if( layer->CreateFeature( feature ) != OGRERR_NONE ) + return CR_FAILURE; + // but we don't care and destroy the feature + OGRFeature::DestroyFeature( feature ); + } + + dataset->CommitTransaction(); + + GDALClose( dataset ); + const auto finish{std::chrono::steady_clock::now()}; + const std::chrono::duration elapsed_seconds{finish - start}; + out.print("done in %f ms !\n", elapsed_seconds.count()); + return CR_OK; +} + +static command_result export_region_tiles(color_ostream &out) +{ out.print("%lu / %d region map tiles loaded\n", world->world_data->midmap_data.region_details.size(), world->world_data->world_width * world->world_data->world_height @@ -169,14 +322,11 @@ static command_result do_command(color_ostream &out, vector ¶meters) create_field(layer, "reanimating", OFTInteger, 0, OFSTBoolean); create_field(layer, "has_bogeymen", OFTInteger, 0, OFSTBoolean); - }catch (const DFHack::command_result& r) { + } catch (const DFHack::command_result& r) { out.printerr("could not create fields for output layer"); return r; } - int wdim = 768; // dimension of a world tile - int rdim = 48; // dimension of a region tile - // iterating over the region details allows the user to do partial map exports // by manually scrolling on the embark site selection for (auto const region_details : world->world_data->midmap_data.region_details) {