Skip to content
Closed
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
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
<p align="center">
<a href="https://go-cart.io"><img src ="img/gocart_logo.svg" width="65%"></a>
</p>
The website can be accessed using this <a href = "https://go-cart.io">link.</a>
This repository contains the underlying C++ program that powers go-cart.io. If you'd only like to create a cartogram, you may find it easier to visit the website directly. Otherwise, if you'd like to make code contributions, feature suggestions and/or play with the inner workings of our cartogram generator, you're at the right place!

This program uses the fast flow-based method developed by Michael T. Gastner, Vivien Seguy, and Pratyush More. For more information, you may refer to the following [paper](https://www.pnas.org/content/115/10/E2156):

Expand Down Expand Up @@ -55,10 +57,11 @@ We manage dependencies with a Python virtual environment and Conan 2. The projec

Only `Debug` build commands are shown below, but the same commands can be run with `Release` build by replacing `Debug` with `Release`.

1. Create a virtual environment with the required dependencies:
1. Create a virtual environment with the required dependencies and activate it:

``` shell
virtualenv .venv && .venv/bin/pip install -r requirements.txt
source .venv/bin/activate
```

2. Setup Conan
Expand Down
14 changes: 14 additions & 0 deletions include/string_to_decimal_converter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,20 @@
#include <string>
#include <vector>

/**
* @brief A utility class for converting string representations of numbers to decimal format.
*
* This class handles various number formats including:
* - Regular decimal numbers (e.g. "123.456", "123,456")
* - Scientific notation (e.g. "1.23e-4", "1.23E4")
* - Special value "NA"
*
* For scientific notation:
* - Both 'e' and 'E' are supported as exponent markers
* - The mantissa can use either '.' or ',' as decimal separator
* - The exponent must be an integer and can be negative
* - The mantissa must be a valid decimal number
*/
class StringToDecimalConverter
{
private:
Expand Down
4 changes: 4 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
conan==2.16.1
cmake==3.30.0
pip
wheel
6 changes: 6 additions & 0 deletions src/cartogram_info/cartogram_info.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,12 @@ void CartogramInfo::write_shifted_insets()

void CartogramInfo::write_svg(const std::string &suffix)
{
// Skip SVG creation if output is being redirected to stdout

if (args_.redirect_exports_to_stdout) {
return;
}

InsetState insets_combined = convert_to_inset_state();
insets_combined.rescale_map();

Expand Down
21 changes: 21 additions & 0 deletions src/draw/draw.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ void InsetState::write_delaunay_triangles(
const std::string &filename,
const bool draw_projected_points)
{



// Skip if redirecting to stdout
if (args_.redirect_exports_to_stdout) {
return;
}
std::cerr << "Writing " << filename << ".svg" << std::endl;

Canvas cvs(filename + ".svg", lx_, ly_);
Expand Down Expand Up @@ -118,6 +125,10 @@ void InsetState::write_delaunay_triangles(

void InsetState::write_quadtree(const std::string &filename)
{
// Skip if redirecting to stdout
if (args_.redirect_exports_to_stdout) {
return;
}
std::cerr << "Writing " << filename << ".svg" << std::endl;

Canvas cvs(filename + ".svg", lx_, ly_);
Expand All @@ -135,6 +146,10 @@ void InsetState::write_quadtree(const std::string &filename)

void InsetState::write_intersections_image()
{
// Skip if redirecting to stdout
if (args_.redirect_exports_to_stdout) {
return;
}
unsigned int res = intersections_resolution;
std::string svg_name = inset_name() + "_intersections_" +
std::to_string(n_finished_integrations()) + ".svg";
Expand Down Expand Up @@ -165,6 +180,12 @@ void InsetState::write_map(
const bool equal_area_map,
const std::unordered_map<Point, Vector> vectors) const
{
std::cerr << "Redirect value: " << args_.redirect_exports_to_stdout << std::endl;

// Skip if redirecting to stdout
if (args_.redirect_exports_to_stdout) {
return;
}
const auto svg_name = file_name + ".svg";
std::cerr << "Writing " << svg_name << std::endl;

Expand Down
4 changes: 4 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@
#include "parse_arguments.hpp"
#include "progress_tracker.hpp"


int main(const int argc, const char *argv[])
{

std::cerr << "Cartogram C++ version 0.1.0" << std::endl;

// Parse command-line arguments
Arguments args = parse_arguments(argc, argv);

Expand Down
90 changes: 76 additions & 14 deletions src/misc/string_to_decimal_converter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const std::string StringToDecimalConverter::NA_ = "NA";

bool StringToDecimalConverter::is_valid_char(char ch)
{
return (std::isdigit(ch)) || ch == point_ || ch == comma_ || ch == minus_;
return (std::isdigit(ch)) || ch == point_ || ch == comma_ || ch == minus_ || ch == 'e' || ch == 'E';
}

std::string StringToDecimalConverter::remove_char(std::string str, char ch)
Expand Down Expand Up @@ -87,22 +87,66 @@ bool StringToDecimalConverter::is_str_valid_characters(const std::string &str)
return true;
}

// Only 0 to 9, '.', '-', and ',' are allowed
// Only 0 to 9, '.', '-', ',', 'e', and 'E' are allowed
for (const auto &c : str) {
if (!is_valid_char(c)) {
return false;
}
}

// '-' can only be used once
if (count_char(str, minus_) > 1) {
return false;
// '-' can only be used once in the mantissa and once in the exponent
size_t first_e = str.find_first_of("eE");
if (first_e != std::string::npos) {
// Check mantissa part
std::string mantissa = str.substr(0, first_e);
if (count_char(mantissa, minus_) > 1) {
return false;
}
if (count_char(mantissa, minus_) == 1 && mantissa[0] != minus_) {
return false;
}
// Check exponent part
std::string exponent = str.substr(first_e + 1);
if (count_char(exponent, minus_) > 1) {
return false;
}
if (count_char(exponent, minus_) == 1 && exponent[0] != minus_) {
return false;
}
} else {
// No scientific notation, check as before
if (count_char(str, minus_) > 1) {
return false;
}
if (count_char(str, minus_) == 1 && str[0] != minus_) {
return false;
}
}

// '-' can only be used at the beginning
if (count_char(str, minus_) == 1 and str[0] != minus_) {
// Check for valid scientific notation format
size_t e_count = count_char(str, 'e') + count_char(str, 'E');
if (e_count > 1) {
return false;
}
if (e_count == 1) {
size_t e_pos = str.find_first_of("eE");
// Must have digits before and after 'e'/'E'
if (e_pos == 0 || e_pos == str.length() - 1) {
return false;
}
// Must have at least one digit after 'e'/'E'
bool has_digit_after = false;
for (size_t i = e_pos + 1; i < str.length(); i++) {
if (std::isdigit(str[i])) {
has_digit_after = true;
break;
}
}
if (!has_digit_after) {
return false;
}
}

return true;
}

Expand All @@ -111,19 +155,37 @@ bool StringToDecimalConverter::is_str_correct_format(const std::string &str)
assert(is_str_valid_characters(str));
assert(is_str_NA(str) == false);

// if the number of commas and points both are more than 1, then this format
// does not belong to any known convention
// Handle scientific notation separately
size_t e_pos = str.find_first_of("eE");
if (e_pos != std::string::npos) {
// Check mantissa part
std::string mantissa = str.substr(0, e_pos);
if (has_multiple_commas_and_points(mantissa)) {
return false;
}
if (has_invalid_comma_point_sequence(mantissa)) {
return false;
}
if (has_separator_at_the_end(mantissa)) {
return false;
}
// Exponent part should only contain digits and optional minus sign
std::string exponent = str.substr(e_pos + 1);
for (char c : exponent) {
if (!std::isdigit(c) && c != minus_) {
return false;
}
}
return true;
}

// Original format validation for non-scientific notation
if (has_multiple_commas_and_points(str)) {
return false;
}

// Check for commas before and after a point, or points before and after a
// comma
if (has_invalid_comma_point_sequence(str)) {
return false;
}

// Check for separators at the end of the string
if (has_separator_at_the_end(str)) {
return false;
}
Expand Down