From 74a6f174b6ab17e73f405bbdaedd05ee1e5bae92 Mon Sep 17 00:00:00 2001
From: Michael Kopp <kopp.michael@yahoo.de>
Date: Thu, 5 Oct 2023 21:23:06 +0200
Subject: [PATCH] introduce binary operators for aggregation

---
 README.md |  3 ++-
 main.cpp  |  6 +++++-
 tile.cpp  | 36 ++++++++++++++++++++++++++++++++++++
 tile.hpp  |  2 ++
 4 files changed, 45 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 5744d5623..906737b10 100644
--- a/README.md
+++ b/README.md
@@ -422,8 +422,9 @@ resolution is obtained than by using a smaller _maxzoom_ or _detail_.
  * `-Y`_attribute_`:`_description_ or `--attribute-description=`_attribute_`:`_description_: Set the `description` for the specified attribute in the tileset metadata to _description_ instead of the usual `String`, `Number`, or `Boolean`.
  * `-E`_attribute_`:`_operation_ or `--accumulate-attribute=`_attribute_`:`_operation_: Preserve the named _attribute_ from features
    that are dropped, coalesced-as-needed, or clustered. The _operation_ may be
-   `sum`, `product`, `mean`, `max`, `min`, `concat`, or `comma`
+   `sum`, `product`, `mean`, `max`, `min`, `and`, `or`, `concat`, or `comma`
    to specify how the named _attribute_ is accumulated onto the attribute of the same name in a feature that does survive.
+   Note, that `and` and `or` are only intended for boolean input.
  * `-pe` or `--empty-csv-columns-are-null`: Treat empty CSV columns as nulls rather than as empty strings.
  * `-aI` or `--convert-stringified-ids-to-numbers`: If a feature ID is the string representation of a number, convert it to a plain number to use as the feature ID.
  * `--use-attribute-for-id=`*name*: Use the attribute with the specified *name* as if it were specified as the feature ID. (If this attribute is a stringified number, you must also use `-aI` to convert it to a number.)
diff --git a/main.cpp b/main.cpp
index ae41d8254..3957c9a2a 100644
--- a/main.cpp
+++ b/main.cpp
@@ -2392,12 +2392,16 @@ void set_attribute_accum(std::map<std::string, attribute_op> &attribute_accum, c
 		t = op_max;
 	} else if (type == "min") {
 		t = op_min;
+	} else if (type == "or") {
+		t = op_or;
+	} else if (type == "and") {
+		t = op_and;
 	} else if (type == "concat") {
 		t = op_concat;
 	} else if (type == "comma") {
 		t = op_comma;
 	} else {
-		fprintf(stderr, "Attribute method (%s) must be sum, product, mean, max, min, concat, or comma\n", type.c_str());
+		fprintf(stderr, "Attribute method (%s) must be sum, product, mean, max, min, or, and, concat, or comma\n", type.c_str());
 		exit(EXIT_FAILURE);
 	}
 
diff --git a/tile.cpp b/tile.cpp
index cf419825a..22bea7b5a 100644
--- a/tile.cpp
+++ b/tile.cpp
@@ -1566,6 +1566,32 @@ void add_tilestats(std::string const &layername, int z, std::vector<std::map<std
 	add_to_file_keys(fk->second.file_keys, key, attrib);
 }
 
+// parse boolean values
+// "true" -> true
+// "false" -> false
+// Anything else is parsed as numeric value using atof;
+//  - if it is not 0 -> true
+//  - if it is 0 (including somethign not parsable, e.g. "foo" or "True") -> false
+bool atob(const char* str) {
+	if (0 == strcmp(str, "true")) {
+		return true;
+	}
+	if (0 == strcmp(str, "false")) {
+		return false;
+	}
+	const double floating_point_value = atof(str);
+	return floating_point_value != 0;
+}
+
+std::string btoa(bool value) {
+	if (value) {
+		return "true";
+	}
+	else {
+		return "false";
+	}
+}
+
 void preserve_attribute(attribute_op op, serial_feature &, char *stringpool, long long *pool_off, std::string &key, serial_val &val, partial &p) {
 	if (p.need_tilestats.count(key) == 0) {
 		p.need_tilestats.insert(key);
@@ -1623,6 +1649,16 @@ void preserve_attribute(attribute_op op, serial_feature &, char *stringpool, lon
 				break;
 			}
 
+			case op_or:
+				p.full_values[i].s = btoa(atob(p.full_values[i].s.c_str()) || atob(val.s.c_str()));
+				p.full_values[i].type = mvt_bool;
+				break;
+
+			case op_and:
+				p.full_values[i].s = btoa(atob(p.full_values[i].s.c_str()) && atob(val.s.c_str()));
+				p.full_values[i].type = mvt_bool;
+				break;
+
 			case op_mean: {
 				auto state = p.attribute_accum_state.find(key);
 				if (state == p.attribute_accum_state.end()) {
diff --git a/tile.hpp b/tile.hpp
index 32fb22772..e057e6f38 100644
--- a/tile.hpp
+++ b/tile.hpp
@@ -17,6 +17,8 @@ enum attribute_op {
 	op_comma,
 	op_max,
 	op_min,
+	op_or,
+	op_and,
 };
 
 long long write_tile(char **geom, char *metabase, char *stringpool, unsigned *file_bbox, int z, unsigned x, unsigned y, int detail, int min_detail, int basezoom, sqlite3 *outdb, const char *outdir, double droprate, int buffer, const char *fname, FILE **geomfile, int file_minzoom, int file_maxzoom, double todo, char *geomstart, long long along, double gamma, int nlayers);