From 2c5bfff9bdd689c2a28a1b27447f70a49afab510 Mon Sep 17 00:00:00 2001 From: nihalzp <81457724+nihalzp@users.noreply.github.com> Date: Tue, 23 Sep 2025 00:12:48 +0800 Subject: [PATCH 01/11] Remove crs from output --- src/cartogram_info/write_geojson.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/cartogram_info/write_geojson.cpp b/src/cartogram_info/write_geojson.cpp index 2154ffc5..7870ef6d 100644 --- a/src/cartogram_info/write_geojson.cpp +++ b/src/cartogram_info/write_geojson.cpp @@ -192,9 +192,6 @@ void CartogramInfo::json_to_geojson( add_dividers_to_geojson(container[(container.size() - 1)]); } - // Add/replace CRS to custom_crs - new_json["crs"] = {{"type", "name"}, {"properties", {{"name", custom_crs}}}}; - new_json["properties"]["note"] = "Created using cartogram-cpp / go-cart.io with custom projection, not in " "EPSG:4326"; From 46aa3eb443374ec6db15186a0bf5cd99cf3ed684 Mon Sep 17 00:00:00 2001 From: nihalzp <81457724+nihalzp@users.noreply.github.com> Date: Tue, 23 Sep 2025 00:14:15 +0800 Subject: [PATCH 02/11] Add projected entry to geojson properties --- src/cartogram_info/write_geojson.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/cartogram_info/write_geojson.cpp b/src/cartogram_info/write_geojson.cpp index 7870ef6d..6128f538 100644 --- a/src/cartogram_info/write_geojson.cpp +++ b/src/cartogram_info/write_geojson.cpp @@ -196,6 +196,9 @@ void CartogramInfo::json_to_geojson( "Created using cartogram-cpp / go-cart.io with custom projection, not in " "EPSG:4326"; + // Write that the map is projected + new_json["properties"]["projected"] = true; + // Iterate over GeoDivs and gd_ids in the container. The index // container.size()-2 is reserved for the bounding box, and the index // container.size()-1 is reserved for the divider lines. Thus, we must From abacf53bb86f6dda5ab94375734094e266d8ec94 Mon Sep 17 00:00:00 2001 From: nihalzp <81457724+nihalzp@users.noreply.github.com> Date: Tue, 23 Sep 2025 00:22:58 +0800 Subject: [PATCH 03/11] Remove thread_local static from albers projection --- src/inset_state/albers_projection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/inset_state/albers_projection.cpp b/src/inset_state/albers_projection.cpp index 15bd1d65..2770d67b 100644 --- a/src/inset_state/albers_projection.cpp +++ b/src/inset_state/albers_projection.cpp @@ -140,7 +140,7 @@ void InsetState::apply_albers_projection() const double phi_1 = 0.5 * (phi_0 + max_lat); const double phi_2 = 0.5 * (phi_0 + min_lat); - static thread_local AlbersProjector proj(lambda_0, phi_0, phi_1, phi_2); + const AlbersProjector proj(lambda_0, phi_0, phi_1, phi_2); transform_points([&](const Point &q) { return proj(q); From be4b5d23a1718ff4fd9cc8948775fee76e517cd8 Mon Sep 17 00:00:00 2001 From: nihalzp <81457724+nihalzp@users.noreply.github.com> Date: Tue, 23 Sep 2025 01:03:07 +0800 Subject: [PATCH 04/11] Remove custom crs --- include/cartogram_info.hpp | 1 + include/constants.hpp | 3 -- src/cartogram_info/cartogram_info.cpp | 1 + src/cartogram_info/read_geojson.cpp | 33 +++++++++++++++---- .../shift_insets_to_position.cpp | 4 +-- 5 files changed, 31 insertions(+), 11 deletions(-) diff --git a/include/cartogram_info.hpp b/include/cartogram_info.hpp index e17f8b13..6688c80f 100644 --- a/include/cartogram_info.hpp +++ b/include/cartogram_info.hpp @@ -18,6 +18,7 @@ class CartogramInfo std::string map_name_; std::map> unique_properties_map_; int id_col_; + bool is_projected_; // TODO: We assume that either all external rings are counterclockwise or // all are clockwise. This dichotomy covers most geospatial boundary diff --git a/include/constants.hpp b/include/constants.hpp index 74a8c6b9..16bd81d4 100644 --- a/include/constants.hpp +++ b/include/constants.hpp @@ -65,7 +65,4 @@ constexpr double xi_sq = 4.0; // ly constexpr unsigned int plotted_cell_length = 8; -// String to identify our custom CRS -constexpr const char *custom_crs = "EPSG:cartesian"; - #endif // CONST_HPP_ diff --git a/src/cartogram_info/cartogram_info.cpp b/src/cartogram_info/cartogram_info.cpp index 5e6deceb..d5651581 100644 --- a/src/cartogram_info/cartogram_info.cpp +++ b/src/cartogram_info/cartogram_info.cpp @@ -10,6 +10,7 @@ CartogramInfo::CartogramInfo(const Arguments args) : args_(args) is_world_map_ = args_.world; timer.start("Total time"); crs_ = "+proj=longlat"; + is_projected_ = false; if (!args.visual_file_name.empty()) { diff --git a/src/cartogram_info/read_geojson.cpp b/src/cartogram_info/read_geojson.cpp index 4925dfc9..7a0e86ce 100644 --- a/src/cartogram_info/read_geojson.cpp +++ b/src/cartogram_info/read_geojson.cpp @@ -202,14 +202,33 @@ static nlohmann::json load_geojson(const std::string &geometry_file_name) } // Read coordinate reference system if it is included in the GeoJSON -static void extract_crs(const nlohmann::json &j, std::string &crs) +static void extract_crs( + const nlohmann::json &j, + std::string &crs, + const bool is_projected) { if ( j.contains("crs") && j["crs"].contains("properties") && j["crs"]["properties"].contains("name")) { crs = j["crs"]["properties"]["name"]; + std::cerr << "Coordinate reference system found: " << crs << std::endl; + return; + } + std::cerr << "No `crs` field found." << std::endl; + if (!is_projected) { + std::cerr << "Default coordinate reference system assumed: " << crs + << std::endl; } - std::cerr << "Coordinate reference system: " << crs << std::endl; +} + +static bool is_projected(const nlohmann::json &j) +{ + if ( + j.contains("properties") && j["properties"].contains("projected") && + j["properties"]["projected"].is_boolean()) { + return j["properties"]["projected"]; + } + return false; } // Extract unique properties from GeoJSON and return them as a map @@ -402,14 +421,16 @@ void CartogramInfo::read_geojson() std::exit(19); } - extract_crs(j, crs_); - // Skip projection, this is an output from our program - if (crs_ == custom_crs) { - std::cerr << "WARNING: " << custom_crs << " detected. " + // If already projected, skip projection + if (is_projected(j)) { + is_projected_ = true; + std::cerr << "WARNING: `projected=true` property detected. " << "Applying --skip_projection flag." << std::endl; args_.skip_projection = true; } + extract_crs(j, crs_, is_projected_); + // If args_.id_col is specified, use that as the sole unique property if (args_.id_col) { diff --git a/src/cartogram_info/shift_insets_to_position.cpp b/src/cartogram_info/shift_insets_to_position.cpp index 12a9e037..2beabd83 100644 --- a/src/cartogram_info/shift_insets_to_position.cpp +++ b/src/cartogram_info/shift_insets_to_position.cpp @@ -7,8 +7,8 @@ void CartogramInfo::reposition_insets(bool output_to_stdout) // Warn user about repositoning insets with `--skip_projection` flag if (args_.skip_projection && n_insets() > 1) { std::cerr << "WARNING: Trying to repostion insets with "; - if (crs_ == custom_crs) { - std::cerr << "custom coordinate reference system " << custom_crs << ". "; + if (is_projected_) { + std::cerr << "original input map already projected. "; } else { std::cerr << "`--skip_projection` flag present. "; } From ad20c759b8119aa5ec8efc9cb0172593177fead1 Mon Sep 17 00:00:00 2001 From: nihalzp <81457724+nihalzp@users.noreply.github.com> Date: Tue, 23 Sep 2025 01:11:12 +0800 Subject: [PATCH 05/11] Update readme.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 66c050d9..05effd63 100644 --- a/README.md +++ b/README.md @@ -177,6 +177,7 @@ For Windows users, we recommend using our program through Windows Subsystem for - If running one of the commands starting with `.venv/bin/cmake` gives you an error, it is likely that a dependency was not installed correctly. Rerun the appropriate commands above to install the required dependencies and try again. If it still fails, make sure you have the virtual environment activated by running `source .venv/bin/activate` in your terminal, and then try again. - If you get an error which mentions permission issues, try running the command that gave you the error with `sudo` prefixed. Alternatively, you may follow the next instruction. - If you still get permission issues or VScode's `CMake: Install` does not work, make sure you own the relevant directories (i.e. `/usr/local/bin` and the working directory). You may assign ownership to your account with `sudo chown -R $(whoami) .`, replacing `.` with the directory of choice. +- Use `cartogram --help` to see all available options. If you are not getting the intended output, review the appropriate flags to check whether any of them are relevant to your use case. The default behavior may not always be what you expect, and explicitly specifying a flag can improve performance. For example, using the `--id` flag will speed up the program; otherwise, it will attempt to automatically detect the identifying property, which may take longer. ### Benchmarking @@ -218,7 +219,7 @@ To push changes to production, please follow the the instructions on [go-cart-io ### Contributing -Contributions are highly encouraged! Please feel free to take a stab at any at any of the open issues and send in a pull request. If you need help getting setup or more guidance contributing, please @ any of the main contributors (@adisidev, @nihalzp, @mgastner) under any of the open issues (or after creating your own issue), and we'll be happy to guide you! +Contributions are highly encouraged! Please feel free to take a stab at any at any of the open issues and send in a pull request. If you need help getting setup or more guidance contributing, please @ any of the main contributors (@nihalzp, @adisidev, @mgastner) under any of the open issues (or after creating your own issue), and we'll be happy to guide you! If you'd like to contribute to the project, please run our tests after you make any changes. From 44b8b5de406ee27ef9649ad59555d1171ed7b74c Mon Sep 17 00:00:00 2001 From: nihalzp <81457724+nihalzp@users.noreply.github.com> Date: Tue, 23 Sep 2025 01:15:36 +0800 Subject: [PATCH 06/11] Increase verbosity --- src/inset_state/integrate.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/inset_state/integrate.cpp b/src/inset_state/integrate.cpp index 81ba7efd..0a663fa3 100644 --- a/src/inset_state/integrate.cpp +++ b/src/inset_state/integrate.cpp @@ -147,6 +147,7 @@ bool InsetState::continue_integrating() const void InsetState::integrate(ProgressTracker &progress_tracker) { + std::cerr << std::endl << "Integrating inset " << pos_ << std::endl; timer.start(inset_name_); From cdcb65f606d5d21a22f3b10481412117309ce3c1 Mon Sep 17 00:00:00 2001 From: nihalzp <81457724+nihalzp@users.noreply.github.com> Date: Tue, 23 Sep 2025 02:07:20 +0800 Subject: [PATCH 07/11] Make sure output coordinates are not very small --- src/cartogram_info/cartogram_info.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/cartogram_info/cartogram_info.cpp b/src/cartogram_info/cartogram_info.cpp index d5651581..f98506f4 100644 --- a/src/cartogram_info/cartogram_info.cpp +++ b/src/cartogram_info/cartogram_info.cpp @@ -323,12 +323,13 @@ void CartogramInfo::rescale_insets() // Iterate over insets and normalize areas for (InsetState &inset_state : inset_states_) { - inset_state.normalize_inset_area(cart_initial_total_target_area()); + inset_state.normalize_inset_area( + default_long_grid_length * default_long_grid_length); // Rescale copy of original map too if (args_.redirect_exports_to_stdout) { inset_state.normalize_inset_area( - cart_initial_total_target_area(), + default_long_grid_length * default_long_grid_length, false, true); } From 512e5c7cb0cb53cace4cbeb69dba70aa2797f36a Mon Sep 17 00:00:00 2001 From: nihalzp <81457724+nihalzp@users.noreply.github.com> Date: Tue, 23 Sep 2025 02:07:40 +0800 Subject: [PATCH 08/11] Only compute area error after normalization --- src/inset_state/integrate.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/inset_state/integrate.cpp b/src/inset_state/integrate.cpp index 0a663fa3..a41f0b00 100644 --- a/src/inset_state/integrate.cpp +++ b/src/inset_state/integrate.cpp @@ -56,15 +56,15 @@ void InsetState::prepare_for_integration() } // Store initial inset area to calculate area drift, - // set area errors based on this initial_area store_initial_area(); - set_area_errors(); // Store initial target area to normalize inset areas store_initial_target_area(); // Normalize total target area to be equal to initial area normalize_target_area(); + + set_area_errors(); } void InsetState::cleanup_after_integration() From dcbb69773f017add3378b97e46220ae4bdd842ee Mon Sep 17 00:00:00 2001 From: nihalzp <81457724+nihalzp@users.noreply.github.com> Date: Tue, 23 Sep 2025 02:21:02 +0800 Subject: [PATCH 09/11] Improve inset positioning logic --- .../shift_insets_to_position.cpp | 43 +++++++++---------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/src/cartogram_info/shift_insets_to_position.cpp b/src/cartogram_info/shift_insets_to_position.cpp index 2beabd83..7cc0b147 100644 --- a/src/cartogram_info/shift_insets_to_position.cpp +++ b/src/cartogram_info/shift_insets_to_position.cpp @@ -31,31 +31,10 @@ void CartogramInfo::reposition_insets(bool output_to_stdout) bboxes.at(inset_pos) = inset_state.bbox(output_to_stdout); } - // Calculate the width and height of all positioned insets without spacing - double width = bboxes.at("C").xmax() - bboxes.at("C").xmin() + - bboxes.at("L").xmax() - bboxes.at("L").xmin() + - bboxes.at("R").xmax() - bboxes.at("R").xmin(); - - // Considering edge cases where the width of the insets named "T" or "B" - // might be greater than the width of "C", "L", "R" insets combined - width = std::max({ - bboxes.at("T").xmax() - bboxes.at("T").xmin(), // width of inset T - bboxes.at("B").xmax() - bboxes.at("B").xmin(), // width of inset B - width // width of inset C + L + R - }); - - // Similarly for height instead of width - double height = bboxes.at("C").ymax() - bboxes.at("C").ymin() + - bboxes.at("T").ymax() - bboxes.at("T").ymin() + - bboxes.at("B").ymax() - bboxes.at("B").ymin(); - height = std::max({ - bboxes.at("R").ymax() - bboxes.at("R").ymin(), // height of inset R - bboxes.at("L").ymax() - bboxes.at("L").ymin(), // height of inset L - height // height of inset C + T + B - }); + const double height_C = bboxes.at("C").ymax() - bboxes.at("C").ymin(); + const double width_C = bboxes.at("C").xmax() - bboxes.at("C").xmin(); // Spacing between insets - const double inset_spacing = std::max(width, height) * inset_spacing_factor; for (InsetState &inset_state : inset_states_) { std::string inset_pos = inset_state.pos(); @@ -70,6 +49,10 @@ void CartogramInfo::reposition_insets(bool output_to_stdout) x = std::max( {bboxes.at("C").xmax(), bboxes.at("B").xmax(), bboxes.at("T").xmax()}); x += bboxes.at("R").xmax(); + + const double width_R = bboxes.at("R").xmax() - bboxes.at("R").xmin(); + const double inset_spacing = (width_C + width_R) * inset_spacing_factor; + x += inset_spacing; } else if (pos == "L") { x = std::min( @@ -77,11 +60,20 @@ void CartogramInfo::reposition_insets(bool output_to_stdout) // At "L", xmin is negative and lies in the 2nd and 3rd quadrant x += bboxes.at("L").xmin(); + + const double width_L = bboxes.at("L").xmax() - bboxes.at("L").xmin(); + const double inset_spacing = (width_C + width_L) * inset_spacing_factor; + x -= inset_spacing; } else if (pos == "T") { y = std::max( {bboxes.at("C").ymax(), bboxes.at("R").ymax(), bboxes.at("L").ymax()}); y += bboxes.at("T").ymax(); + + const double height_T = bboxes.at("T").ymax() - bboxes.at("T").ymin(); + const double inset_spacing = + (height_C + height_T) * inset_spacing_factor; + y += inset_spacing; } else if (pos == "B") { y = std::min( @@ -89,6 +81,11 @@ void CartogramInfo::reposition_insets(bool output_to_stdout) // At "B", ymin is negative and lies in the 3rd and 4th quadrant y += bboxes.at("B").ymin(); + + const double height_B = bboxes.at("B").ymax() - bboxes.at("B").ymin(); + const double inset_spacing = + (height_C + height_B) * inset_spacing_factor; + y -= inset_spacing; } From c143f469739165a1f02e9709efe6d8aa22ae74e0 Mon Sep 17 00:00:00 2001 From: nihalzp <81457724+nihalzp@users.noreply.github.com> Date: Tue, 23 Sep 2025 02:33:02 +0800 Subject: [PATCH 10/11] Update perf classification threshold --- .github/scripts/gen_perf_comment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/gen_perf_comment.py b/.github/scripts/gen_perf_comment.py index db9784a2..c1dc0ba8 100644 --- a/.github/scripts/gen_perf_comment.py +++ b/.github/scripts/gen_perf_comment.py @@ -3,7 +3,7 @@ from scipy.stats import t import html, re -THRESH = 0.03 +THRESH = 0.05 ALPHA = 0.05 From 60f4d9f05510cbf847bd945280a65d8596a60e2c Mon Sep 17 00:00:00 2001 From: nihalzp <81457724+nihalzp@users.noreply.github.com> Date: Tue, 23 Sep 2025 02:35:53 +0800 Subject: [PATCH 11/11] Bump version to 25.9 --- src/misc/parse_arguments.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/misc/parse_arguments.cpp b/src/misc/parse_arguments.cpp index 79bc055d..cf78e3dd 100644 --- a/src/misc/parse_arguments.cpp +++ b/src/misc/parse_arguments.cpp @@ -8,7 +8,7 @@ Arguments parse_arguments(const int argc, const char *argv[]) // Create parser for arguments using argparse. // From https://github.com/p-ranav/argparse - argparse::ArgumentParser arguments("./cartogram", "25.8"); + argparse::ArgumentParser arguments("./cartogram", "25.9"); // Positional argument accepting geometry file (GeoJSON, JSON) as input arguments.add_argument("geometry_file")