From 5c57d5aef2d1fcf97a304d9eb0bf88fd4ff7e9d8 Mon Sep 17 00:00:00 2001 From: Erica Fischer Date: Mon, 4 Nov 2024 12:17:25 -0800 Subject: [PATCH 01/29] Sketching out sqlite options for tile-join --- tile-join.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tile-join.cpp b/tile-join.cpp index 0fe39297..486a0313 100644 --- a/tile-join.cpp +++ b/tile-join.cpp @@ -72,6 +72,9 @@ struct stats { std::vector strategies{}; }; +// https://stackoverflow.com/questions/11753871/getting-the-type-of-a-column-in-sqlite +std::string get_column_types_query = "SELECT m.name AS table_name, UPPER(m.type) AS table_type, p.name AS column_name, p.type AS data_type, CASE p.pk WHEN 1 THEN 'PRIMARY KEY' END AS const FROM sqlite_master AS m INNER JOIN pragma_table_info(m.name) AS p WHERE m.name NOT IN ('sqlite_sequence') ORDER BY m.name, p.cid;"; + void append_tile(std::string message, int z, unsigned x, unsigned y, std::map &layermap, std::vector &header, std::map> &mapping, std::set &exclude, std::set &include, std::set &keep_layers, std::set &remove_layers, int ifmatched, mvt_tile &outtile, json_object *filter) { mvt_tile tile; int features_added = 0; @@ -1196,6 +1199,10 @@ int main(int argc, char **argv) { int filearg = 0; json_object *filter = NULL; + std::string join_sqlite_fname; + std::string join_tile_column; + std::string join_table_column; + struct tileset_reader *readers = NULL; CPUS = sysconf(_SC_NPROCESSORS_ONLN); @@ -1243,6 +1250,10 @@ int main(int argc, char **argv) { {"rename-layer", required_argument, 0, 'R'}, {"read-from", required_argument, 0, 'r'}, + {"join-sqlite", required_argument, 0, '~'}, + {"join-tile-key", required_argument, 0, '~'}, + {"join-table-key", required_argument, 0, '~'}, + {"no-tile-size-limit", no_argument, &pk, 1}, {"no-tile-compression", no_argument, &pC, 1}, {"empty-csv-columns-are-null", no_argument, &pe, 1}, @@ -1427,6 +1438,8 @@ int main(int argc, char **argv) { max_tilestats_values = atoi(optarg); } else if (strcmp(opt, "unidecode-data") == 0) { unidecode_data = read_unidecode(optarg); + } else if (strcmp(opt, "join-sqlite") == 0) { + join_sqlite_fname = optarg; } else { fprintf(stderr, "%s: Unrecognized option --%s\n", argv[0], opt); exit(EXIT_ARGS); From a07aad9db720af9205c76195414a2745efef1b17 Mon Sep 17 00:00:00 2001 From: Erica Fischer Date: Mon, 2 Dec 2024 14:21:53 -0800 Subject: [PATCH 02/29] Enable sqlite3 serialized multithreading --- tile-join.cpp | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/tile-join.cpp b/tile-join.cpp index 5e8a2bb0..facfda01 100644 --- a/tile-join.cpp +++ b/tile-join.cpp @@ -75,7 +75,7 @@ struct stats { // https://stackoverflow.com/questions/11753871/getting-the-type-of-a-column-in-sqlite std::string get_column_types_query = "SELECT m.name AS table_name, UPPER(m.type) AS table_type, p.name AS column_name, p.type AS data_type, CASE p.pk WHEN 1 THEN 'PRIMARY KEY' END AS const FROM sqlite_master AS m INNER JOIN pragma_table_info(m.name) AS p WHERE m.name NOT IN ('sqlite_sequence') ORDER BY m.name, p.cid;"; -void append_tile(std::string message, int z, unsigned x, unsigned y, std::map &layermap, std::vector &header, std::map> &mapping, std::set &exclude, std::set &include, std::set &keep_layers, std::set &remove_layers, int ifmatched, mvt_tile &outtile, json_object *filter) { +void append_tile(std::string message, int z, unsigned x, unsigned y, std::map &layermap, std::vector &header, std::map> &mapping, sqlite3 *db, std::set &exclude, std::set &include, std::set &keep_layers, std::set &remove_layers, int ifmatched, mvt_tile &outtile, json_object *filter) { mvt_tile tile; int features_added = 0; bool was_compressed; @@ -746,6 +746,7 @@ struct arg { std::vector *header = NULL; std::map> *mapping = NULL; + sqlite3 *db = NULL; std::set *exclude = NULL; std::set *include = NULL; std::set *keep_layers = NULL; @@ -762,7 +763,7 @@ void *join_worker(void *v) { mvt_tile tile; for (size_t i = 0; i < ai->second.size(); i++) { - append_tile(ai->second[i], ai->first.z, ai->first.x, ai->first.y, *(a->layermap), *(a->header), *(a->mapping), *(a->exclude), *(a->include), *(a->keep_layers), *(a->remove_layers), a->ifmatched, tile, a->filter); + append_tile(ai->second[i], ai->first.z, ai->first.x, ai->first.y, *(a->layermap), *(a->header), *(a->mapping), a->db, *(a->exclude), *(a->include), *(a->keep_layers), *(a->remove_layers), a->ifmatched, tile, a->filter); } ai->second.clear(); @@ -797,7 +798,7 @@ void *join_worker(void *v) { return NULL; } -void dispatch_tasks(std::map> &tasks, std::vector> &layermaps, sqlite3 *outdb, const char *outdir, std::vector &header, std::map> &mapping, std::set &exclude, std::set &include, int ifmatched, std::set &keep_layers, std::set &remove_layers, json_object *filter, struct tileset_reader *readers) { +void dispatch_tasks(std::map> &tasks, std::vector> &layermaps, sqlite3 *outdb, const char *outdir, std::vector &header, std::map> &mapping, sqlite3 *db, std::set &exclude, std::set &include, int ifmatched, std::set &keep_layers, std::set &remove_layers, json_object *filter, struct tileset_reader *readers) { pthread_t pthreads[CPUS]; std::vector args; @@ -807,6 +808,7 @@ void dispatch_tasks(std::map> &tasks, std::vector< args[i].layermap = &layermaps[i]; args[i].header = &header; args[i].mapping = &mapping; + args[i].db = db; args[i].exclude = &exclude; args[i].include = &include; args[i].keep_layers = &keep_layers; @@ -947,7 +949,7 @@ void handle_vector_layers(json_object *vector_layers, std::map &layermap, sqlite3 *outdb, const char *outdir, struct stats *st, std::vector &header, std::map> &mapping, std::set &exclude, std::set &include, int ifmatched, std::string &attribution, std::string &description, std::set &keep_layers, std::set &remove_layers, std::string &name, json_object *filter, std::map &attribute_descriptions, std::string &generator_options, std::vector *strategies) { +void decode(struct tileset_reader *readers, std::map &layermap, sqlite3 *outdb, const char *outdir, struct stats *st, std::vector &header, std::map> &mapping, sqlite3 *db, std::set &exclude, std::set &include, int ifmatched, std::string &attribution, std::string &description, std::set &keep_layers, std::set &remove_layers, std::string &name, json_object *filter, std::map &attribute_descriptions, std::string &generator_options, std::vector *strategies) { std::vector> layermaps; for (size_t i = 0; i < CPUS; i++) { layermaps.push_back(std::map()); @@ -1016,7 +1018,7 @@ void decode(struct tileset_reader *readers, std::mapzoom != current.first.z || readers->x != current.first.x || readers->y != current.first.y) { if (tasks.size() > 100 * CPUS) { - dispatch_tasks(tasks, layermaps, outdb, outdir, header, mapping, exclude, include, ifmatched, keep_layers, remove_layers, filter, readers); + dispatch_tasks(tasks, layermaps, outdb, outdir, header, mapping, db, exclude, include, ifmatched, keep_layers, remove_layers, filter, readers); tasks.clear(); } } @@ -1045,7 +1047,7 @@ void decode(struct tileset_reader *readers, std::mapminlat2 = min(minlat, st->minlat2); st->maxlat2 = max(maxlat, st->maxlat2); - dispatch_tasks(tasks, layermaps, outdb, outdir, header, mapping, exclude, include, ifmatched, keep_layers, remove_layers, filter, readers); + dispatch_tasks(tasks, layermaps, outdb, outdir, header, mapping, db, exclude, include, ifmatched, keep_layers, remove_layers, filter, readers); layermap = merge_layermaps(layermaps); struct tileset_reader *next; @@ -1200,9 +1202,9 @@ int main(int argc, char **argv) { int filearg = 0; json_object *filter = NULL; - std::string join_sqlite_fname; - std::string join_tile_column; - std::string join_table_column; + std::string join_sqlite_fname; + std::string join_tile_column; + std::string join_table_column; struct tileset_reader *readers = NULL; @@ -1216,8 +1218,14 @@ int main(int argc, char **argv) { CPUS = 1; } + if (sqlite3_config(SQLITE_CONFIG_SERIALIZED) != SQLITE_OK) { + fprintf(stderr, "Could not enable sqlite3 serialized multithreading\n"); + exit(EXIT_SQLITE); + } + std::vector header; std::map> mapping; + sqlite3 *db = NULL; std::set exclude; std::set include; @@ -1522,7 +1530,7 @@ int main(int argc, char **argv) { std::string generator_options; std::vector strategies; - decode(readers, layermap, outdb, out_dir, &st, header, mapping, exclude, include, ifmatched, attribution, description, keep_layers, remove_layers, name, filter, attribute_descriptions, generator_options, &strategies); + decode(readers, layermap, outdb, out_dir, &st, header, mapping, db, exclude, include, ifmatched, attribution, description, keep_layers, remove_layers, name, filter, attribute_descriptions, generator_options, &strategies); if (set_attribution.size() != 0) { attribution = set_attribution; From 36b207c16e45dc809da4b16da9d9ecb525786b9a Mon Sep 17 00:00:00 2001 From: Erica Fischer Date: Tue, 3 Dec 2024 14:43:02 -0800 Subject: [PATCH 03/29] Fix some unnecessary round trips from std::string to char * and back --- tile-join.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/tile-join.cpp b/tile-join.cpp index facfda01..f687e386 100644 --- a/tile-join.cpp +++ b/tile-join.cpp @@ -55,6 +55,8 @@ int minzoom = 0; std::map renames; bool exclude_all = false; std::vector unidecode_data; +std::string join_tile_column; +std::string join_table_column; bool want_overzoom = false; int buffer = 5; @@ -144,9 +146,9 @@ void append_tile(std::string message, int z, unsigned x, unsigned y, std::map exclude_attributes; + mvt_feature &feat = layer.features[f]; + std::set exclude_attributes; if (filter != NULL && !evaluate(feat, layer, filter, exclude_attributes, z, unidecode_data)) { continue; } @@ -163,7 +165,7 @@ void append_tile(std::string message, int z, unsigned x, unsigned y, std::map key_order; for (size_t t = 0; t + 1 < feat.tags.size(); t += 2) { - const char *key = layer.keys[feat.tags[t]].c_str(); + const std::string &key = layer.keys[feat.tags[t]]; mvt_value &val = layer.values[feat.tags[t + 1]]; serial_val sv = mvt_value_to_serial_val(val); @@ -171,12 +173,15 @@ void append_tile(std::string message, int z, unsigned x, unsigned y, std::map>(key, std::pair(val, sv))); key_order.push_back(key); } - if (header.size() > 0 && strcmp(key, header[0].c_str()) == 0) { + if (key == join_tile_column) { + } + + if (header.size() > 0 && key == header[0]) { std::map>::iterator ii = mapping.find(sv.s); if (ii != mapping.end()) { @@ -1203,8 +1208,6 @@ int main(int argc, char **argv) { json_object *filter = NULL; std::string join_sqlite_fname; - std::string join_tile_column; - std::string join_table_column; struct tileset_reader *readers = NULL; From 0ae73c1143aa7672b28324405316fbbcab1834d0 Mon Sep 17 00:00:00 2001 From: Erica Fischer Date: Tue, 3 Dec 2024 16:16:41 -0800 Subject: [PATCH 04/29] Gathering join keys for sql query --- tile-join.cpp | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/tile-join.cpp b/tile-join.cpp index f687e386..110d44c6 100644 --- a/tile-join.cpp +++ b/tile-join.cpp @@ -57,6 +57,7 @@ bool exclude_all = false; std::vector unidecode_data; std::string join_tile_column; std::string join_table_column; +std::string join_table; bool want_overzoom = false; int buffer = 5; @@ -143,6 +144,27 @@ void append_tile(std::string message, int z, unsigned x, unsigned y, std::map join_keys; + join_keys.resize(layer.features.size()); + + for (size_t f = 0; f < layer.features.size(); f++) { + mvt_feature &feat = layer.features[f]; + join_keys[f].type = mvt_no_such_key; + + for (size_t t = 0; t + 1 < feat.tags.size(); t += 2) { + const std::string &key = layer.keys[feat.tags[t]]; + if (key == join_tile_column) { + const mvt_value &val = layer.values[feat.tags[t + 1]]; + join_keys[f] = val; + break; + } + } + } + } + auto tilestats = layermap.find(layer.name); for (size_t f = 0; f < layer.features.size(); f++) { @@ -1263,8 +1285,9 @@ int main(int argc, char **argv) { {"read-from", required_argument, 0, 'r'}, {"join-sqlite", required_argument, 0, '~'}, - {"join-tile-key", required_argument, 0, '~'}, - {"join-table-key", required_argument, 0, '~'}, + {"join-tile-column", required_argument, 0, '~'}, + {"join-table-column", required_argument, 0, '~'}, + {"join-table", required_argument, 0, '~'}, {"no-tile-size-limit", no_argument, &pk, 1}, {"no-tile-compression", no_argument, &pC, 1}, @@ -1452,6 +1475,12 @@ int main(int argc, char **argv) { unidecode_data = read_unidecode(optarg); } else if (strcmp(opt, "join-sqlite") == 0) { join_sqlite_fname = optarg; + } else if (strcmp(opt, "join-table") == 0) { + join_table = optarg; + } else if (strcmp(opt, "join-table-column") == 0) { + join_table_column = optarg; + } else if (strcmp(opt, "join-tile-column") == 0) { + join_tile_column = optarg; } else { fprintf(stderr, "%s: Unrecognized option --%s\n", argv[0], opt); exit(EXIT_ARGS); From 264665b0ce51990c5d5ccd27021e29bcda6b6b84 Mon Sep 17 00:00:00 2001 From: Erica Fischer Date: Thu, 5 Dec 2024 13:52:28 -0800 Subject: [PATCH 05/29] Make a query --- tile-join.cpp | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tile-join.cpp b/tile-join.cpp index 110d44c6..623631d4 100644 --- a/tile-join.cpp +++ b/tile-join.cpp @@ -78,6 +78,37 @@ struct stats { // https://stackoverflow.com/questions/11753871/getting-the-type-of-a-column-in-sqlite std::string get_column_types_query = "SELECT m.name AS table_name, UPPER(m.type) AS table_type, p.name AS column_name, p.type AS data_type, CASE p.pk WHEN 1 THEN 'PRIMARY KEY' END AS const FROM sqlite_master AS m INNER JOIN pragma_table_info(m.name) AS p WHERE m.name NOT IN ('sqlite_sequence') ORDER BY m.name, p.cid;"; +std::vector> get_joined_rows(sqlite3 *db, const std::vector &join_keys) { + std::vector> ret; + ret.resize(join_keys.size()); + + // double quotes for table and column identifiers + const char *s = sqlite3_mprintf("select * from %w where %w in (", join_table.c_str(), join_table_column.c_str()); + std::string query = s; + sqlite3_free((void *) s); + + for (size_t i = 0; i < join_keys.size(); i++) { + const mvt_value &v = join_keys[i]; + + // single quotes for literals + if (v.type == mvt_string) { + s = sqlite3_mprintf("%q", v.c_str()); + query += s; + sqlite3_free((void *) s); + } else { + query += v.toString(); + } + + if (i + 1 < join_keys.size()) { + query += ", "; + } + } + + query += ");"; + + return ret; +} + void append_tile(std::string message, int z, unsigned x, unsigned y, std::map &layermap, std::vector &header, std::map> &mapping, sqlite3 *db, std::set &exclude, std::set &include, std::set &keep_layers, std::set &remove_layers, int ifmatched, mvt_tile &outtile, json_object *filter) { mvt_tile tile; int features_added = 0; @@ -163,6 +194,8 @@ void append_tile(std::string message, int z, unsigned x, unsigned y, std::map> joined = get_joined_rows(db, join_keys); } auto tilestats = layermap.find(layer.name); From ad47b93a815eae762c59c964337ce8b9b0ceb681 Mon Sep 17 00:00:00 2001 From: Erica Fischer Date: Thu, 5 Dec 2024 14:42:48 -0800 Subject: [PATCH 06/29] Actually open the gpkg. Fix the query quoting. --- tile-join.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tile-join.cpp b/tile-join.cpp index 623631d4..afe6fdb0 100644 --- a/tile-join.cpp +++ b/tile-join.cpp @@ -83,7 +83,7 @@ std::vector> get_joined_rows(sqlite3 *db, const ret.resize(join_keys.size()); // double quotes for table and column identifiers - const char *s = sqlite3_mprintf("select * from %w where %w in (", join_table.c_str(), join_table_column.c_str()); + const char *s = sqlite3_mprintf("select * from \"%w\" where \"%w\" in (", join_table.c_str(), join_table_column.c_str()); std::string query = s; sqlite3_free((void *) s); @@ -92,7 +92,7 @@ std::vector> get_joined_rows(sqlite3 *db, const // single quotes for literals if (v.type == mvt_string) { - s = sqlite3_mprintf("%q", v.c_str()); + s = sqlite3_mprintf("'%q'", v.c_str()); query += s; sqlite3_free((void *) s); } else { @@ -105,6 +105,7 @@ std::vector> get_joined_rows(sqlite3 *db, const } query += ");"; + printf("%s\n", query.c_str()); return ret; } @@ -1508,6 +1509,10 @@ int main(int argc, char **argv) { unidecode_data = read_unidecode(optarg); } else if (strcmp(opt, "join-sqlite") == 0) { join_sqlite_fname = optarg; + if (sqlite3_open(optarg, &db) != SQLITE_OK) { + fprintf(stderr, "%s: %s\n", optarg, sqlite3_errmsg(db)); + exit(EXIT_SQLITE); + } } else if (strcmp(opt, "join-table") == 0) { join_table = optarg; } else if (strcmp(opt, "join-table-column") == 0) { From cbcdbc5516346c9ba2cae8999dbc64b096ecde7b Mon Sep 17 00:00:00 2001 From: Erica Fischer Date: Thu, 5 Dec 2024 16:01:27 -0800 Subject: [PATCH 07/29] Actually do the query and get results back --- tile-join.cpp | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tile-join.cpp b/tile-join.cpp index afe6fdb0..06ccc38a 100644 --- a/tile-join.cpp +++ b/tile-join.cpp @@ -77,6 +77,7 @@ struct stats { // https://stackoverflow.com/questions/11753871/getting-the-type-of-a-column-in-sqlite std::string get_column_types_query = "SELECT m.name AS table_name, UPPER(m.type) AS table_type, p.name AS column_name, p.type AS data_type, CASE p.pk WHEN 1 THEN 'PRIMARY KEY' END AS const FROM sqlite_master AS m INNER JOIN pragma_table_info(m.name) AS p WHERE m.name NOT IN ('sqlite_sequence') ORDER BY m.name, p.cid;"; +// select name, type from pragma_table_info('parsed'); std::vector> get_joined_rows(sqlite3 *db, const std::vector &join_keys) { std::vector> ret; @@ -107,6 +108,37 @@ std::vector> get_joined_rows(sqlite3 *db, const query += ");"; printf("%s\n", query.c_str()); + sqlite3_stmt *stmt; + if (sqlite3_prepare_v2(db, query.c_str(), -1, &stmt, NULL) != SQLITE_OK) { + fprintf(stderr, "sqlite3 query %s failed: %s\n", query.c_str(), sqlite3_errmsg(db)); + exit(EXIT_SQLITE); + } + while (sqlite3_step(stmt) == SQLITE_ROW) { + int count = sqlite3_column_count(stmt); + std::map row; + + for (int i = 0; i < count; i++) { + int type = sqlite3_column_type(stmt, i); + mvt_value v; + v.type = mvt_null; + + if (type == SQLITE_INTEGER || type == SQLITE_FLOAT) { + v = mvt_value(sqlite3_column_double(stmt, i)); + } else if (type == SQLITE_TEXT || type == SQLITE_BLOB) { + v.set_string_value((const char *) sqlite3_column_text(stmt, i)); + } + + const char *name = sqlite3_column_name(stmt, i); + row.emplace(name, v); + + printf("%s -> %s\n", name, v.toString().c_str()); + } + } + if (sqlite3_finalize(stmt) != SQLITE_OK) { + fprintf(stderr, "sqlite3 finalize failed: %s\n", sqlite3_errmsg(db)); + exit(EXIT_SQLITE); + } + return ret; } From a4b866833a524ec5d6e1c2283ec068e234460338 Mon Sep 17 00:00:00 2001 From: Erica Fischer Date: Thu, 5 Dec 2024 16:19:54 -0800 Subject: [PATCH 08/29] Join the attributes onto the feature --- tile-join.cpp | 60 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 22 deletions(-) diff --git a/tile-join.cpp b/tile-join.cpp index 06ccc38a..c45aaad2 100644 --- a/tile-join.cpp +++ b/tile-join.cpp @@ -75,19 +75,17 @@ struct stats { std::vector strategies{}; }; -// https://stackoverflow.com/questions/11753871/getting-the-type-of-a-column-in-sqlite -std::string get_column_types_query = "SELECT m.name AS table_name, UPPER(m.type) AS table_type, p.name AS column_name, p.type AS data_type, CASE p.pk WHEN 1 THEN 'PRIMARY KEY' END AS const FROM sqlite_master AS m INNER JOIN pragma_table_info(m.name) AS p WHERE m.name NOT IN ('sqlite_sequence') ORDER BY m.name, p.cid;"; -// select name, type from pragma_table_info('parsed'); - std::vector> get_joined_rows(sqlite3 *db, const std::vector &join_keys) { std::vector> ret; ret.resize(join_keys.size()); // double quotes for table and column identifiers - const char *s = sqlite3_mprintf("select * from \"%w\" where \"%w\" in (", join_table.c_str(), join_table_column.c_str()); + const char *s = sqlite3_mprintf("select \"%w\", * from \"%w\" where \"%w\" in (", + join_table_column.c_str(), join_table.c_str(), join_table_column.c_str()); std::string query = s; sqlite3_free((void *) s); + std::map key_to_row; for (size_t i = 0; i < join_keys.size(); i++) { const mvt_value &v = join_keys[i]; @@ -96,8 +94,11 @@ std::vector> get_joined_rows(sqlite3 *db, const s = sqlite3_mprintf("'%q'", v.c_str()); query += s; sqlite3_free((void *) s); + key_to_row.emplace(v.get_string_value(), i); } else { - query += v.toString(); + std::string stringified = v.toString(); + key_to_row.emplace(stringified, i); + query += stringified; } if (i + 1 < join_keys.size()) { @@ -106,7 +107,6 @@ std::vector> get_joined_rows(sqlite3 *db, const } query += ");"; - printf("%s\n", query.c_str()); sqlite3_stmt *stmt; if (sqlite3_prepare_v2(db, query.c_str(), -1, &stmt, NULL) != SQLITE_OK) { @@ -117,21 +117,31 @@ std::vector> get_joined_rows(sqlite3 *db, const int count = sqlite3_column_count(stmt); std::map row; - for (int i = 0; i < count; i++) { - int type = sqlite3_column_type(stmt, i); - mvt_value v; - v.type = mvt_null; - - if (type == SQLITE_INTEGER || type == SQLITE_FLOAT) { - v = mvt_value(sqlite3_column_double(stmt, i)); - } else if (type == SQLITE_TEXT || type == SQLITE_BLOB) { - v.set_string_value((const char *) sqlite3_column_text(stmt, i)); + if (count > 0) { + // join key is 0th column of query + std::string key = (const char *) sqlite3_column_text(stmt, 0); + auto f = key_to_row.find(key); + if (f == key_to_row.end()) { + fprintf(stderr, "Unexpected join key: %s\n", key.c_str()); + continue; } - const char *name = sqlite3_column_name(stmt, i); - row.emplace(name, v); + for (int i = 1; i < count; i++) { + int type = sqlite3_column_type(stmt, i); + mvt_value v; + v.type = mvt_null; + + if (type == SQLITE_INTEGER || type == SQLITE_FLOAT) { + v = mvt_value(sqlite3_column_double(stmt, i)); + } else if (type == SQLITE_TEXT || type == SQLITE_BLOB) { + v.set_string_value((const char *) sqlite3_column_text(stmt, i)); + } - printf("%s -> %s\n", name, v.toString().c_str()); + const char *name = sqlite3_column_name(stmt, i); + row.emplace(name, v); + } + + ret[f->second] = std::move(row); } } if (sqlite3_finalize(stmt) != SQLITE_OK) { @@ -208,6 +218,7 @@ void append_tile(std::string message, int z, unsigned x, unsigned y, std::map> joined; if (db != NULL) { // collect join keys for sql query @@ -228,7 +239,7 @@ void append_tile(std::string message, int z, unsigned x, unsigned y, std::map> joined = get_joined_rows(db, join_keys); + joined = get_joined_rows(db, join_keys); } auto tilestats = layermap.find(layer.name); @@ -266,7 +277,13 @@ void append_tile(std::string message, int z, unsigned x, unsigned y, std::map>(kv.first, std::pair(kv.second, mvt_value_to_serial_val(kv.second)))); + key_order.push_back(kv.first); + } + } } if (header.size() > 0 && key == header[0]) { @@ -312,7 +329,6 @@ void append_tile(std::string message, int z, unsigned x, unsigned y, std::map>(joinkey, std::pair(outval, outsv))); From c88f8c07e78288df56992f951c1586c8f0526049 Mon Sep 17 00:00:00 2001 From: Erica Fischer Date: Tue, 17 Dec 2024 14:43:04 -0800 Subject: [PATCH 09/29] Set matched if the sql join matches --- tile-join.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tile-join.cpp b/tile-join.cpp index c45aaad2..fcfd1e34 100644 --- a/tile-join.cpp +++ b/tile-join.cpp @@ -282,6 +282,7 @@ void append_tile(std::string message, int z, unsigned x, unsigned y, std::map>(kv.first, std::pair(kv.second, mvt_value_to_serial_val(kv.second)))); key_order.push_back(kv.first); + matched = true; } } } From 03daaefd8e02495590e34bdc46ae631d5377f3eb Mon Sep 17 00:00:00 2001 From: Erica Fischer Date: Tue, 17 Dec 2024 15:01:06 -0800 Subject: [PATCH 10/29] Add a flag to get the feature ID from the query --- tile-join.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tile-join.cpp b/tile-join.cpp index fcfd1e34..78c2573f 100644 --- a/tile-join.cpp +++ b/tile-join.cpp @@ -58,6 +58,7 @@ std::vector unidecode_data; std::string join_tile_column; std::string join_table_column; std::string join_table; +std::string attribute_for_id; bool want_overzoom = false; int buffer = 5; @@ -279,7 +280,10 @@ void append_tile(std::string message, int z, unsigned x, unsigned y, std::map>(kv.first, std::pair(kv.second, mvt_value_to_serial_val(kv.second)))); key_order.push_back(kv.first); matched = true; @@ -1371,6 +1375,7 @@ int main(int argc, char **argv) { {"join-tile-column", required_argument, 0, '~'}, {"join-table-column", required_argument, 0, '~'}, {"join-table", required_argument, 0, '~'}, + {"use-attribute-for-id", required_argument, 0, '~'}, {"no-tile-size-limit", no_argument, &pk, 1}, {"no-tile-compression", no_argument, &pC, 1}, @@ -1568,6 +1573,8 @@ int main(int argc, char **argv) { join_table_column = optarg; } else if (strcmp(opt, "join-tile-column") == 0) { join_tile_column = optarg; + } else if (strcmp(opt, "use-attribute-for-id") == 0) { + attribute_for_id = optarg; } else { fprintf(stderr, "%s: Unrecognized option --%s\n", argv[0], opt); exit(EXIT_ARGS); From fcc611adc4b822640483fe3f4156e6b537d8a641 Mon Sep 17 00:00:00 2001 From: Erica Fischer Date: Tue, 17 Dec 2024 15:22:34 -0800 Subject: [PATCH 11/29] Observe attribute exclusion when joining from sql queries --- tile-join.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tile-join.cpp b/tile-join.cpp index 78c2573f..ce4e436a 100644 --- a/tile-join.cpp +++ b/tile-join.cpp @@ -279,14 +279,19 @@ void append_tile(std::string message, int z, unsigned x, unsigned y, std::map 0) { + matched = true; + } + for (auto const &kv : joined[f]) { if (kv.first == attribute_for_id) { outfeature.has_id = true; outfeature.id = mvt_value_to_long_long(kv.second); - } else if (kv.second.type != mvt_null) { - attributes.insert(std::pair>(kv.first, std::pair(kv.second, mvt_value_to_serial_val(kv.second)))); - key_order.push_back(kv.first); - matched = true; + } else if (include.count(kv.first) || (!exclude_all && exclude.count(kv.first) == 0 && exclude_attributes.count(kv.first) == 0)) { + if (kv.second.type != mvt_null) { + attributes.insert(std::pair>(kv.first, std::pair(kv.second, mvt_value_to_serial_val(kv.second)))); + key_order.push_back(kv.first); + } } } } From 5c8f1545a55303db8fc6f7736b4040d2f0327ff5 Mon Sep 17 00:00:00 2001 From: Erica Fischer Date: Tue, 17 Dec 2024 15:43:34 -0800 Subject: [PATCH 12/29] Add a flag to exclude all attributes from the tile side of the join --- tile-join.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tile-join.cpp b/tile-join.cpp index 7fef6fb8..cdcabc1e 100644 --- a/tile-join.cpp +++ b/tile-join.cpp @@ -55,6 +55,7 @@ int maxzoom = 32; int minzoom = 0; std::map renames; bool exclude_all = false; +bool exclude_all_tile_attributes = false; std::vector unidecode_data; std::string join_tile_column; std::string join_table_column; @@ -274,9 +275,11 @@ void append_tile(std::string message, int z, unsigned x, unsigned y, std::map>(key, std::pair(val, sv))); - key_order.push_back(key); + if (!exclude_all_tile_attributes) { + if (include.count(key) || (!exclude_all && exclude.count(key) == 0 && exclude_attributes.count(key) == 0)) { + attributes.insert(std::pair>(key, std::pair(val, sv))); + key_order.push_back(key); + } } if (f < joined.size()) { @@ -1367,6 +1370,7 @@ int main(int argc, char **argv) { {"exclude", required_argument, 0, 'x'}, {"exclude-all", no_argument, 0, 'X'}, {"include", required_argument, 0, 'y'}, + {"exclude-all-tile-attributes", no_argument, 0, '~'}, {"layer", required_argument, 0, 'l'}, {"exclude-layer", required_argument, 0, 'L'}, {"quiet", no_argument, 0, 'q'}, @@ -1581,6 +1585,8 @@ int main(int argc, char **argv) { join_tile_column = optarg; } else if (strcmp(opt, "use-attribute-for-id") == 0) { attribute_for_id = optarg; + } else if (strcmp(opt, "exclude-all-tile-attributes") == 0) { + exclude_all_tile_attributes = true; } else { fprintf(stderr, "%s: Unrecognized option --%s\n", argv[0], opt); exit(EXIT_ARGS); From d6ce92422f60c7a32e137fac269814b13860ad3d Mon Sep 17 00:00:00 2001 From: Erica Fischer Date: Thu, 19 Dec 2024 16:16:52 -0800 Subject: [PATCH 13/29] Make tile-join bounding boxes reflect feature bounds, not tile bounds --- tests/join-population/joined-i.mbtiles.json | 4 +- .../joined-no-tile-stats.mbtiles.json | 4 +- .../join-population/joined-null.mbtiles.json | 4 +- ...d-tile-stats-attributes-limit.mbtiles.json | 4 +- ...ile-stats-sample-values-limit.mbtiles.json | 4 +- ...oined-tile-stats-values-limit.mbtiles.json | 4 +- tests/join-population/joined.mbtiles.json | 4 +- .../macarthur-6-9-exclude.mbtiles.json | 4 +- .../macarthur-6-9.mbtiles.json | 4 +- tests/join-population/merged.mbtiles.json | 4 +- tile-join.cpp | 196 ++++++++++-------- 11 files changed, 124 insertions(+), 112 deletions(-) diff --git a/tests/join-population/joined-i.mbtiles.json b/tests/join-population/joined-i.mbtiles.json index 35a15fd7..e50d42e9 100644 --- a/tests/join-population/joined-i.mbtiles.json +++ b/tests/join-population/joined-i.mbtiles.json @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "-122.343750,37.857507,-122.255859,37.926868", -"bounds": "-122.343750,37.857507,-122.255859,37.926868", +"antimeridian_adjusted_bounds": "-135.000000,37.881357,-122.280579,40.979898", +"bounds": "-135.000000,37.881357,-122.280579,40.979898", "center": "-122.299805,37.892187,12", "description": "tests/join-population/tabblock_06001420.mbtiles", "format": "pbf", diff --git a/tests/join-population/joined-no-tile-stats.mbtiles.json b/tests/join-population/joined-no-tile-stats.mbtiles.json index 6f7cde11..2e36d31e 100644 --- a/tests/join-population/joined-no-tile-stats.mbtiles.json +++ b/tests/join-population/joined-no-tile-stats.mbtiles.json @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "-122.343750,37.857507,-122.255859,37.926868", -"bounds": "-122.343750,37.857507,-122.255859,37.926868", +"antimeridian_adjusted_bounds": "-135.000000,37.877021,-122.280579,40.979898", +"bounds": "-135.000000,37.877021,-122.280579,40.979898", "center": "-122.299805,37.892187,12", "description": "tests/join-population/tabblock_06001420.mbtiles", "format": "pbf", diff --git a/tests/join-population/joined-null.mbtiles.json b/tests/join-population/joined-null.mbtiles.json index 8339f194..a2ff7cc4 100644 --- a/tests/join-population/joined-null.mbtiles.json +++ b/tests/join-population/joined-null.mbtiles.json @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "-122.343750,37.857507,-122.255859,37.926868", -"bounds": "-122.343750,37.857507,-122.255859,37.926868", +"antimeridian_adjusted_bounds": "-135.000000,37.877021,-122.280579,40.979898", +"bounds": "-135.000000,37.877021,-122.280579,40.979898", "center": "-122.299805,37.892187,12", "description": "tests/join-population/tabblock_06001420.mbtiles", "format": "pbf", diff --git a/tests/join-population/joined-tile-stats-attributes-limit.mbtiles.json b/tests/join-population/joined-tile-stats-attributes-limit.mbtiles.json index 37b5326a..89245614 100644 --- a/tests/join-population/joined-tile-stats-attributes-limit.mbtiles.json +++ b/tests/join-population/joined-tile-stats-attributes-limit.mbtiles.json @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "-122.343750,37.857507,-122.255859,37.926868", -"bounds": "-122.343750,37.857507,-122.255859,37.926868", +"antimeridian_adjusted_bounds": "-135.000000,37.877021,-122.280579,40.979898", +"bounds": "-135.000000,37.877021,-122.280579,40.979898", "center": "-122.299805,37.892187,12", "description": "tests/join-population/tabblock_06001420.mbtiles", "format": "pbf", diff --git a/tests/join-population/joined-tile-stats-sample-values-limit.mbtiles.json b/tests/join-population/joined-tile-stats-sample-values-limit.mbtiles.json index 4b217de0..dad62899 100644 --- a/tests/join-population/joined-tile-stats-sample-values-limit.mbtiles.json +++ b/tests/join-population/joined-tile-stats-sample-values-limit.mbtiles.json @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "-122.343750,37.857507,-122.255859,37.926868", -"bounds": "-122.343750,37.857507,-122.255859,37.926868", +"antimeridian_adjusted_bounds": "-135.000000,37.877021,-122.280579,40.979898", +"bounds": "-135.000000,37.877021,-122.280579,40.979898", "center": "-122.299805,37.892187,12", "description": "tests/join-population/tabblock_06001420.mbtiles", "format": "pbf", diff --git a/tests/join-population/joined-tile-stats-values-limit.mbtiles.json b/tests/join-population/joined-tile-stats-values-limit.mbtiles.json index 9fa16025..75172022 100644 --- a/tests/join-population/joined-tile-stats-values-limit.mbtiles.json +++ b/tests/join-population/joined-tile-stats-values-limit.mbtiles.json @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "-122.343750,37.857507,-122.255859,37.926868", -"bounds": "-122.343750,37.857507,-122.255859,37.926868", +"antimeridian_adjusted_bounds": "-135.000000,37.877021,-122.280579,40.979898", +"bounds": "-135.000000,37.877021,-122.280579,40.979898", "center": "-122.299805,37.892187,12", "description": "tests/join-population/tabblock_06001420.mbtiles", "format": "pbf", diff --git a/tests/join-population/joined.mbtiles.json b/tests/join-population/joined.mbtiles.json index 16ad55ee..c1232739 100644 --- a/tests/join-population/joined.mbtiles.json +++ b/tests/join-population/joined.mbtiles.json @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "-122.343750,37.857507,-122.255859,37.926868", -"bounds": "-122.343750,37.857507,-122.255859,37.926868", +"antimeridian_adjusted_bounds": "-135.000000,37.877021,-122.280579,40.979898", +"bounds": "-135.000000,37.877021,-122.280579,40.979898", "center": "-122.299805,37.892187,12", "description": "tests/join-population/tabblock_06001420.mbtiles", "format": "pbf", diff --git a/tests/join-population/macarthur-6-9-exclude.mbtiles.json b/tests/join-population/macarthur-6-9-exclude.mbtiles.json index 0e191929..3f3f0eea 100644 --- a/tests/join-population/macarthur-6-9-exclude.mbtiles.json +++ b/tests/join-population/macarthur-6-9-exclude.mbtiles.json @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "-122.343750,37.439974,-121.992188,37.996163", -"bounds": "-122.343750,37.439974,-121.992188,37.996163", +"antimeridian_adjusted_bounds": "-122.294655,37.695231,-122.104111,37.833107", +"bounds": "-122.294655,37.695231,-122.104111,37.833107", "center": "-122.167969,37.833010,9", "description": "tests/join-population/macarthur.mbtiles", "format": "pbf", diff --git a/tests/join-population/macarthur-6-9.mbtiles.json b/tests/join-population/macarthur-6-9.mbtiles.json index 3f79712d..944a79d9 100644 --- a/tests/join-population/macarthur-6-9.mbtiles.json +++ b/tests/join-population/macarthur-6-9.mbtiles.json @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "-122.343750,37.439974,-121.992188,37.996163", -"bounds": "-122.343750,37.439974,-121.992188,37.996163", +"antimeridian_adjusted_bounds": "-122.294655,37.695231,-122.104111,37.833107", +"bounds": "-122.294655,37.695231,-122.104111,37.833107", "center": "-122.167969,37.833010,9", "description": "tests/join-population/macarthur.mbtiles", "format": "pbf", diff --git a/tests/join-population/merged.mbtiles.json b/tests/join-population/merged.mbtiles.json index e2106996..e7ccaf9c 100644 --- a/tests/join-population/merged.mbtiles.json +++ b/tests/join-population/merged.mbtiles.json @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "237.656250,37.857507,237.744141,37.926868", -"bounds": "-122.343750,37.695438,-122.104097,37.926868", +"antimeridian_adjusted_bounds": "-135.000000,37.694688,-122.103424,40.979898", +"bounds": "-135.000000,37.694688,-122.103424,40.979898", "center": "-122.299805,37.892187,12", "description": "tests/join-population/tabblock_06001420.mbtiles", "format": "pbf", diff --git a/tile-join.cpp b/tile-join.cpp index cdcabc1e..a6483ac2 100644 --- a/tile-join.cpp +++ b/tile-join.cpp @@ -155,7 +155,29 @@ std::vector> get_joined_rows(sqlite3 *db, const return ret; } -void append_tile(std::string message, int z, unsigned x, unsigned y, std::map &layermap, std::vector &header, std::map> &mapping, sqlite3 *db, std::set &exclude, std::set &include, std::set &keep_layers, std::set &remove_layers, int ifmatched, mvt_tile &outtile, json_object *filter) { +struct arg { + std::map> inputs{}; + std::map outputs{}; + + std::map *layermap = NULL; + + std::vector *header = NULL; + std::map> *mapping = NULL; + sqlite3 *db = NULL; + std::set *exclude = NULL; + std::set *include = NULL; + std::set *keep_layers = NULL; + std::set *remove_layers = NULL; + int ifmatched = 0; + json_object *filter = NULL; + struct tileset_reader *readers = NULL; + + double minlat, minlon; + double maxlat, maxlon; + double minlon2, maxlon2; +}; + +void append_tile(std::string message, int z, unsigned x, unsigned y, std::map &layermap, std::vector &header, std::map> &mapping, sqlite3 *db, std::set &exclude, std::set &include, std::set &keep_layers, std::set &remove_layers, int ifmatched, mvt_tile &outtile, json_object *filter, struct arg *a) { mvt_tile tile; int features_added = 0; bool was_compressed; @@ -247,6 +269,12 @@ void append_tile(std::string message, int z, unsigned x, unsigned y, std::mapsecond.minzoom) { @@ -401,26 +442,42 @@ void append_tile(std::string message, int z, unsigned x, unsigned y, std::mapminlat = std::min(a->minlat, std::min(lat1, lat2)); + a->minlon = std::min(a->minlon, std::min(lon1, lon2)); + a->maxlat = std::max(a->maxlat, std::max(lat1, lat2)); + a->maxlon = std::max(a->maxlon, std::max(lon1, lon2)); + + if (lon1 < 0 || lon2 < 0) { + lon1 += 360; + lon2 += 360; + } -double min(double a, double b) { - if (a < b) { - return a; - } else { - return b; + a->minlon2 = std::min(a->minlon2, std::min(lon1, lon2)); + a->maxlon2 = std::max(a->maxlon2, std::max(lon1, lon2)); + } } -} -double max(double a, double b) { - if (a > b) { - return a; - } else { - return b; + if (features_added == 0) { + return; } } @@ -861,24 +918,6 @@ struct tileset_reader *begin_reading(char *fname) { return r; } -struct arg { - std::map> inputs{}; - std::map outputs{}; - - std::map *layermap = NULL; - - std::vector *header = NULL; - std::map> *mapping = NULL; - sqlite3 *db = NULL; - std::set *exclude = NULL; - std::set *include = NULL; - std::set *keep_layers = NULL; - std::set *remove_layers = NULL; - int ifmatched = 0; - json_object *filter = NULL; - struct tileset_reader *readers = NULL; -}; - void *join_worker(void *v) { arg *a = (arg *) v; @@ -886,7 +925,7 @@ void *join_worker(void *v) { mvt_tile tile; for (size_t i = 0; i < ai->second.size(); i++) { - append_tile(ai->second[i], ai->first.z, ai->first.x, ai->first.y, *(a->layermap), *(a->header), *(a->mapping), a->db, *(a->exclude), *(a->include), *(a->keep_layers), *(a->remove_layers), a->ifmatched, tile, a->filter); + append_tile(ai->second[i], ai->first.z, ai->first.x, ai->first.y, *(a->layermap), *(a->header), *(a->mapping), a->db, *(a->exclude), *(a->include), *(a->keep_layers), *(a->remove_layers), a->ifmatched, tile, a->filter, a); } ai->second.clear(); @@ -921,7 +960,7 @@ void *join_worker(void *v) { return NULL; } -void dispatch_tasks(std::map> &tasks, std::vector> &layermaps, sqlite3 *outdb, const char *outdir, std::vector &header, std::map> &mapping, sqlite3 *db, std::set &exclude, std::set &include, int ifmatched, std::set &keep_layers, std::set &remove_layers, json_object *filter, struct tileset_reader *readers) { +void dispatch_tasks(std::map> &tasks, std::vector> &layermaps, sqlite3 *outdb, const char *outdir, std::vector &header, std::map> &mapping, sqlite3 *db, std::set &exclude, std::set &include, int ifmatched, std::set &keep_layers, std::set &remove_layers, json_object *filter, struct tileset_reader *readers, double *minlat, double *minlon, double *maxlat, double *maxlon, double *minlon2, double *maxlon2) { pthread_t pthreads[CPUS]; std::vector args; @@ -939,6 +978,12 @@ void dispatch_tasks(std::map> &tasks, std::vector< args[i].ifmatched = ifmatched; args[i].filter = filter; args[i].readers = readers; + args[i].minlat = *minlat; + args[i].minlon = *minlon; + args[i].maxlat = *maxlat; + args[i].maxlon = *maxlon; + args[i].minlon2 = *minlon2; + args[i].maxlon2 = *maxlon2; } size_t count = 0; @@ -967,6 +1012,13 @@ void dispatch_tasks(std::map> &tasks, std::vector< for (size_t i = 0; i < CPUS; i++) { void *retval; + *minlat = std::min(*minlat, args[i].minlat); + *minlon = std::min(*minlon, args[i].minlon); + *maxlat = std::max(*maxlat, args[i].maxlat); + *maxlon = std::max(*maxlon, args[i].maxlon); + *minlon2 = std::min(*minlon2, args[i].minlon2); + *maxlon2 = std::max(*maxlon2, args[i].maxlon2); + if (pthread_join(pthreads[i], &retval) != 0) { perror("pthread_join"); } @@ -1085,36 +1137,10 @@ void decode(struct tileset_reader *readers, std::mapall_done()) { std::pair current = readers->current(); - if (current.first.z != zoom_for_bbox) { - // Only use highest zoom for bbox calculation - // to avoid z0 always covering the world - - minlat = minlon = minlon2 = INT_MAX; - maxlat = maxlon = maxlon2 = INT_MIN; - zoom_for_bbox = current.first.z; - } - - double lat1, lon1, lat2, lon2; - tile2lonlat(current.first.x, current.first.y, current.first.z, &lon1, &lat1); - tile2lonlat(current.first.x + 1, current.first.y + 1, current.first.z, &lon2, &lat2); - minlat = min(lat2, minlat); - minlon = min(lon1, minlon); - maxlat = max(lat1, maxlat); - maxlon = max(lon2, maxlon); - - if (lon1 < 0) { - lon1 += 360; - lon2 += 360; - } - - minlon2 = min(lon1, minlon2); - maxlon2 = max(lon2, maxlon2); - if (current.first.z >= minzoom && current.first.z <= maxzoom) { zxy tile = current.first; if (tasks.count(tile) == 0) { @@ -1141,7 +1167,7 @@ void decode(struct tileset_reader *readers, std::mapzoom != current.first.z || readers->x != current.first.x || readers->y != current.first.y) { if (tasks.size() > 100 * CPUS) { - dispatch_tasks(tasks, layermaps, outdb, outdir, header, mapping, db, exclude, include, ifmatched, keep_layers, remove_layers, filter, readers); + dispatch_tasks(tasks, layermaps, outdb, outdir, header, mapping, db, exclude, include, ifmatched, keep_layers, remove_layers, filter, readers, &minlat, &minlon, &maxlat, &maxlon, &minlon2, &maxlon2); tasks.clear(); } } @@ -1160,18 +1186,18 @@ void decode(struct tileset_reader *readers, std::mapminlon = min(minlon, st->minlon); - st->maxlon = max(maxlon, st->maxlon); - st->minlat = min(minlat, st->minlat); - st->maxlat = max(maxlat, st->maxlat); + dispatch_tasks(tasks, layermaps, outdb, outdir, header, mapping, db, exclude, include, ifmatched, keep_layers, remove_layers, filter, readers, &minlat, &minlon, &maxlat, &maxlon, &minlon2, &maxlon2); + layermap = merge_layermaps(layermaps); - st->minlon2 = min(minlon2, st->minlon2); - st->maxlon2 = max(maxlon2, st->maxlon2); - st->minlat2 = min(minlat, st->minlat2); - st->maxlat2 = max(maxlat, st->maxlat2); + st->minlon = std::min(minlon, st->minlon); + st->maxlon = std::max(maxlon, st->maxlon); + st->minlat = std::min(minlat, st->minlat); + st->maxlat = std::max(maxlat, st->maxlat); - dispatch_tasks(tasks, layermaps, outdb, outdir, header, mapping, db, exclude, include, ifmatched, keep_layers, remove_layers, filter, readers); - layermap = merge_layermaps(layermaps); + st->minlon2 = std::min(minlon2, st->minlon2); + st->maxlon2 = std::max(maxlon2, st->maxlon2); + st->minlat2 = std::min(minlat, st->minlat2); + st->maxlat2 = std::max(maxlat, st->maxlat2); struct tileset_reader *next; for (struct tileset_reader *r = readers; r != NULL; r = next) { @@ -1181,14 +1207,14 @@ void decode(struct tileset_reader *readers, std::mapdb, "SELECT value from metadata where name = 'minzoom'", -1, &stmt, NULL) == SQLITE_OK) { if (sqlite3_step(stmt) == SQLITE_ROW) { - int minz = max(sqlite3_column_int(stmt, 0), minzoom); - st->minzoom = min(st->minzoom, minz); + int minz = std::max(sqlite3_column_int(stmt, 0), minzoom); + st->minzoom = std::min(st->minzoom, minz); } sqlite3_finalize(stmt); } if (sqlite3_prepare_v2(r->db, "SELECT value from metadata where name = 'maxzoom'", -1, &stmt, NULL) == SQLITE_OK) { if (sqlite3_step(stmt) == SQLITE_ROW) { - int maxz = min(sqlite3_column_int(stmt, 0), maxzoom); + int maxz = std::min(sqlite3_column_int(stmt, 0), maxzoom); if (!want_overzoom) { if (st->maxzoom >= 0 && maxz != st->maxzoom) { @@ -1196,7 +1222,7 @@ void decode(struct tileset_reader *readers, std::mapmaxzoom = max(st->maxzoom, maxz); + st->maxzoom = std::max(st->maxzoom, maxz); } sqlite3_finalize(stmt); } @@ -1243,20 +1269,6 @@ void decode(struct tileset_reader *readers, std::mapdb, "SELECT value from metadata where name = 'bounds'", -1, &stmt, NULL) == SQLITE_OK) { - if (sqlite3_step(stmt) == SQLITE_ROW) { - const unsigned char *s = sqlite3_column_text(stmt, 0); - if (s != NULL) { - if (sscanf((char *) s, "%lf,%lf,%lf,%lf", &minlon, &minlat, &maxlon, &maxlat) == 4) { - st->minlon = min(minlon, st->minlon); - st->maxlon = max(maxlon, st->maxlon); - st->minlat = min(minlat, st->minlat); - st->maxlat = max(maxlat, st->maxlat); - } - } - } - sqlite3_finalize(stmt); - } if (sqlite3_prepare_v2(r->db, "SELECT value from metadata where name = 'json'", -1, &stmt, NULL) == SQLITE_OK) { if (sqlite3_step(stmt) == SQLITE_ROW) { const unsigned char *s = sqlite3_column_text(stmt, 0); From acffe23b9729fc3699e16e17b58c1a3c8b69db37 Mon Sep 17 00:00:00 2001 From: Erica Fischer Date: Thu, 19 Dec 2024 16:18:58 -0800 Subject: [PATCH 14/29] More tests --- tests/feature-filter/out/filtered.json.standard | 4 ++-- tests/feature-filter/out/places-filter.mbtiles.json.standard | 4 ++-- tests/join-population/concat.mbtiles.json | 4 ++-- tests/join-population/just-macarthur.mbtiles.json | 4 ++-- tests/join-population/merged-folder.mbtiles.json | 4 ++-- tests/join-population/no-macarthur.mbtiles.json | 4 ++-- tests/join-population/raw-merged-folder.json | 4 ++-- tests/join-population/renamed.mbtiles.json | 4 ++-- tests/join-population/windows.mbtiles.json | 4 ++-- 9 files changed, 18 insertions(+), 18 deletions(-) diff --git a/tests/feature-filter/out/filtered.json.standard b/tests/feature-filter/out/filtered.json.standard index 73fb672d..32a6089b 100644 --- a/tests/feature-filter/out/filtered.json.standard +++ b/tests/feature-filter/out/filtered.json.standard @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "-180.000000,-85.051129,180.000000,85.051129", -"bounds": "-180.000000,-85.051129,180.000000,85.051129", +"antimeridian_adjusted_bounds": "2147483647.000000,2147483647.000000,-2147483648.000000,-2147483648.000000", +"bounds": "2147483647.000000,2147483647.000000,-2147483648.000000,-2147483648.000000", "center": "0.000000,0.000000,0", "description": "tests/feature-filter/out/all.mbtiles", "format": "pbf", diff --git a/tests/feature-filter/out/places-filter.mbtiles.json.standard b/tests/feature-filter/out/places-filter.mbtiles.json.standard index 9936c097..ab8b5a1f 100644 --- a/tests/feature-filter/out/places-filter.mbtiles.json.standard +++ b/tests/feature-filter/out/places-filter.mbtiles.json.standard @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "-175.781250,-42.032974,180.000000,64.168107", -"bounds": "-175.781250,-42.032974,180.000000,64.168107", +"antimeridian_adjusted_bounds": "-175.220947,-41.302571,179.217224,64.168107", +"bounds": "-175.220947,-41.302571,179.217224,64.168107", "center": "-62.578125,17.307462,8", "description": "tests/feature-filter/out/places.mbtiles", "format": "pbf", diff --git a/tests/join-population/concat.mbtiles.json b/tests/join-population/concat.mbtiles.json index 28a2a402..e96d535c 100644 --- a/tests/join-population/concat.mbtiles.json +++ b/tests/join-population/concat.mbtiles.json @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "-180.000000,-85.051129,180.000000,85.051129", -"bounds": "-180.000000,-85.051129,180.000000,85.051129", +"antimeridian_adjusted_bounds": "2147483647.000000,2147483647.000000,-2147483648.000000,-2147483648.000000", +"bounds": "2147483647.000000,2147483647.000000,-2147483648.000000,-2147483648.000000", "center": "-122.104097,37.695438,0", "description": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "format": "pbf", diff --git a/tests/join-population/just-macarthur.mbtiles.json b/tests/join-population/just-macarthur.mbtiles.json index 97b1b7e1..c1a48b33 100644 --- a/tests/join-population/just-macarthur.mbtiles.json +++ b/tests/join-population/just-macarthur.mbtiles.json @@ -1,7 +1,7 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "237.656250,37.857507,237.744141,37.926868", +"antimeridian_adjusted_bounds": "-122.294655,37.694688,-122.103424,37.833649", "attribution": "macarthur's attribution", -"bounds": "-122.343750,37.695438,-122.104097,37.926868", +"bounds": "-122.294655,37.694688,-122.103424,37.833649", "center": "-122.299805,37.892187,11", "description": "macarthur description", "format": "pbf", diff --git a/tests/join-population/merged-folder.mbtiles.json b/tests/join-population/merged-folder.mbtiles.json index 86337ba1..c0ae4f27 100644 --- a/tests/join-population/merged-folder.mbtiles.json +++ b/tests/join-population/merged-folder.mbtiles.json @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "237.656250,37.857507,237.744141,37.926868", -"bounds": "-122.343750,37.695438,-122.104097,37.926868", +"antimeridian_adjusted_bounds": "-135.000000,37.694688,-122.103424,40.979898", +"bounds": "-135.000000,37.694688,-122.103424,40.979898", "center": "-122.299805,37.892187,12", "description": "tests/join-population/tabblock_06001420-folder", "format": "pbf", diff --git a/tests/join-population/no-macarthur.mbtiles.json b/tests/join-population/no-macarthur.mbtiles.json index 80a22b9e..6ffa3601 100644 --- a/tests/join-population/no-macarthur.mbtiles.json +++ b/tests/join-population/no-macarthur.mbtiles.json @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "237.656250,37.857507,237.744141,37.926868", -"bounds": "-122.343750,37.695438,-122.104097,37.926868", +"antimeridian_adjusted_bounds": "-135.000000,37.877021,-122.280579,40.979898", +"bounds": "-135.000000,37.877021,-122.280579,40.979898", "center": "-122.299805,37.892187,12", "description": "tests/join-population/tabblock_06001420.mbtiles", "format": "pbf", diff --git a/tests/join-population/raw-merged-folder.json b/tests/join-population/raw-merged-folder.json index 2c7607ce..d0782293 100644 --- a/tests/join-population/raw-merged-folder.json +++ b/tests/join-population/raw-merged-folder.json @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "237.656250,37.857507,237.744141,37.926868", -"bounds": "-122.343750,37.695438,-122.104097,37.926868", +"antimeridian_adjusted_bounds": "-135.000000,37.694688,-122.103424,40.979898", +"bounds": "-135.000000,37.694688,-122.103424,40.979898", "center": "-122.299805,37.892187,12", "description": "tests/join-population/tabblock_06001420.mbtiles", "format": "pbf", diff --git a/tests/join-population/renamed.mbtiles.json b/tests/join-population/renamed.mbtiles.json index dc74bcfb..727b83ff 100644 --- a/tests/join-population/renamed.mbtiles.json +++ b/tests/join-population/renamed.mbtiles.json @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "-122.343750,37.439974,-121.992188,37.996163", -"bounds": "-122.343750,37.439974,-121.992188,37.996163", +"antimeridian_adjusted_bounds": "-122.294655,37.695231,-122.103424,37.833107", +"bounds": "-122.294655,37.695231,-122.103424,37.833107", "center": "-122.167969,37.828608,10", "description": "tests/join-population/macarthur2.mbtiles", "format": "pbf", diff --git a/tests/join-population/windows.mbtiles.json b/tests/join-population/windows.mbtiles.json index ef2dcc14..b095f46a 100644 --- a/tests/join-population/windows.mbtiles.json +++ b/tests/join-population/windows.mbtiles.json @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "-122.343750,37.439974,-121.992188,37.996163", -"bounds": "-122.343750,37.439974,-121.992188,37.996163", +"antimeridian_adjusted_bounds": "-122.294655,37.695231,-122.103424,37.833107", +"bounds": "-122.294655,37.695231,-122.103424,37.833107", "center": "-122.167969,37.833010,10", "description": "tests/join-population/macarthur.mbtiles", "format": "pbf", From 7338c4b6b06964f1797e6546833e41b41cf7f5eb Mon Sep 17 00:00:00 2001 From: Erica Fischer Date: Thu, 19 Dec 2024 16:24:01 -0800 Subject: [PATCH 15/29] An empty tileset has empty bounds at null island --- tests/feature-filter/out/filtered.json.standard | 4 ++-- tests/join-population/concat.mbtiles.json | 4 ++-- tile-join.cpp | 4 ++++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/feature-filter/out/filtered.json.standard b/tests/feature-filter/out/filtered.json.standard index 32a6089b..460d6256 100644 --- a/tests/feature-filter/out/filtered.json.standard +++ b/tests/feature-filter/out/filtered.json.standard @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "2147483647.000000,2147483647.000000,-2147483648.000000,-2147483648.000000", -"bounds": "2147483647.000000,2147483647.000000,-2147483648.000000,-2147483648.000000", +"antimeridian_adjusted_bounds": "0.000000,0.000000,0.000000,0.000000", +"bounds": "0.000000,0.000000,0.000000,0.000000", "center": "0.000000,0.000000,0", "description": "tests/feature-filter/out/all.mbtiles", "format": "pbf", diff --git a/tests/join-population/concat.mbtiles.json b/tests/join-population/concat.mbtiles.json index e96d535c..1d18b97a 100644 --- a/tests/join-population/concat.mbtiles.json +++ b/tests/join-population/concat.mbtiles.json @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "2147483647.000000,2147483647.000000,-2147483648.000000,-2147483648.000000", -"bounds": "2147483647.000000,2147483647.000000,-2147483648.000000,-2147483648.000000", +"antimeridian_adjusted_bounds": "0.000000,0.000000,0.000000,0.000000", +"bounds": "0.000000,0.000000,0.000000,0.000000", "center": "-122.104097,37.695438,0", "description": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "format": "pbf", diff --git a/tile-join.cpp b/tile-join.cpp index a6483ac2..f8c5552e 100644 --- a/tile-join.cpp +++ b/tile-join.cpp @@ -1711,6 +1711,10 @@ int main(int argc, char **argv) { } } + if (st.maxlon < st.minlon) { + st.maxlon = st.minlon = st.maxlat = st.minlat = st.minlon2 = st.maxlon2 = st.minlat2 = st.maxlat2 = 0; + } + if (st.maxlon - st.minlon <= st.maxlon2 - st.minlon2) { st.minlon2 = st.minlon; st.maxlon2 = st.maxlon; From 4788b67a54f197d5eb30de904386124f98560c92 Mon Sep 17 00:00:00 2001 From: Erica Fischer Date: Fri, 20 Dec 2024 09:42:09 -0800 Subject: [PATCH 16/29] Gather the results from each thread *after* the thread finishes --- tests/feature-filter/out/filtered.json.standard | 4 ++-- tests/join-population/concat.mbtiles.json | 4 ++-- tests/join-population/joined-no-tile-stats.mbtiles.json | 4 ++-- tests/join-population/joined-null.mbtiles.json | 4 ++-- .../joined-tile-stats-attributes-limit.mbtiles.json | 4 ++-- .../joined-tile-stats-sample-values-limit.mbtiles.json | 4 ++-- .../joined-tile-stats-values-limit.mbtiles.json | 4 ++-- tests/join-population/joined.mbtiles.json | 4 ++-- tests/join-population/macarthur-6-9-exclude.mbtiles.json | 4 ++-- tests/join-population/macarthur-6-9.mbtiles.json | 4 ++-- tests/join-population/no-macarthur.mbtiles.json | 4 ++-- tests/join-population/renamed.mbtiles.json | 4 ++-- tests/join-population/windows.mbtiles.json | 4 ++-- tile-join.cpp | 8 ++++---- 14 files changed, 30 insertions(+), 30 deletions(-) diff --git a/tests/feature-filter/out/filtered.json.standard b/tests/feature-filter/out/filtered.json.standard index 460d6256..4d9ae0dd 100644 --- a/tests/feature-filter/out/filtered.json.standard +++ b/tests/feature-filter/out/filtered.json.standard @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "0.000000,0.000000,0.000000,0.000000", -"bounds": "0.000000,0.000000,0.000000,0.000000", +"antimeridian_adjusted_bounds": "-180.000000,0.000000,0.966797,85.051129", +"bounds": "-180.000000,0.000000,0.966797,85.051129", "center": "0.000000,0.000000,0", "description": "tests/feature-filter/out/all.mbtiles", "format": "pbf", diff --git a/tests/join-population/concat.mbtiles.json b/tests/join-population/concat.mbtiles.json index 1d18b97a..156596d7 100644 --- a/tests/join-population/concat.mbtiles.json +++ b/tests/join-population/concat.mbtiles.json @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "0.000000,0.000000,0.000000,0.000000", -"bounds": "0.000000,0.000000,0.000000,0.000000", +"antimeridian_adjusted_bounds": "-122.255859,37.718590,-122.080078,37.857507", +"bounds": "-122.255859,37.718590,-122.080078,37.857507", "center": "-122.104097,37.695438,0", "description": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "format": "pbf", diff --git a/tests/join-population/joined-no-tile-stats.mbtiles.json b/tests/join-population/joined-no-tile-stats.mbtiles.json index 2e36d31e..92a87d2e 100644 --- a/tests/join-population/joined-no-tile-stats.mbtiles.json +++ b/tests/join-population/joined-no-tile-stats.mbtiles.json @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "-135.000000,37.877021,-122.280579,40.979898", -"bounds": "-135.000000,37.877021,-122.280579,40.979898", +"antimeridian_adjusted_bounds": "-135.000000,37.874853,-122.280579,40.979898", +"bounds": "-135.000000,37.874853,-122.280579,40.979898", "center": "-122.299805,37.892187,12", "description": "tests/join-population/tabblock_06001420.mbtiles", "format": "pbf", diff --git a/tests/join-population/joined-null.mbtiles.json b/tests/join-population/joined-null.mbtiles.json index a2ff7cc4..3d2a912e 100644 --- a/tests/join-population/joined-null.mbtiles.json +++ b/tests/join-population/joined-null.mbtiles.json @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "-135.000000,37.877021,-122.280579,40.979898", -"bounds": "-135.000000,37.877021,-122.280579,40.979898", +"antimeridian_adjusted_bounds": "-135.000000,37.874853,-122.280579,40.979898", +"bounds": "-135.000000,37.874853,-122.280579,40.979898", "center": "-122.299805,37.892187,12", "description": "tests/join-population/tabblock_06001420.mbtiles", "format": "pbf", diff --git a/tests/join-population/joined-tile-stats-attributes-limit.mbtiles.json b/tests/join-population/joined-tile-stats-attributes-limit.mbtiles.json index 89245614..7afa4f11 100644 --- a/tests/join-population/joined-tile-stats-attributes-limit.mbtiles.json +++ b/tests/join-population/joined-tile-stats-attributes-limit.mbtiles.json @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "-135.000000,37.877021,-122.280579,40.979898", -"bounds": "-135.000000,37.877021,-122.280579,40.979898", +"antimeridian_adjusted_bounds": "-135.000000,37.874853,-122.280579,40.979898", +"bounds": "-135.000000,37.874853,-122.280579,40.979898", "center": "-122.299805,37.892187,12", "description": "tests/join-population/tabblock_06001420.mbtiles", "format": "pbf", diff --git a/tests/join-population/joined-tile-stats-sample-values-limit.mbtiles.json b/tests/join-population/joined-tile-stats-sample-values-limit.mbtiles.json index dad62899..b0b4c3f8 100644 --- a/tests/join-population/joined-tile-stats-sample-values-limit.mbtiles.json +++ b/tests/join-population/joined-tile-stats-sample-values-limit.mbtiles.json @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "-135.000000,37.877021,-122.280579,40.979898", -"bounds": "-135.000000,37.877021,-122.280579,40.979898", +"antimeridian_adjusted_bounds": "-135.000000,37.874853,-122.280579,40.979898", +"bounds": "-135.000000,37.874853,-122.280579,40.979898", "center": "-122.299805,37.892187,12", "description": "tests/join-population/tabblock_06001420.mbtiles", "format": "pbf", diff --git a/tests/join-population/joined-tile-stats-values-limit.mbtiles.json b/tests/join-population/joined-tile-stats-values-limit.mbtiles.json index 75172022..517f1f24 100644 --- a/tests/join-population/joined-tile-stats-values-limit.mbtiles.json +++ b/tests/join-population/joined-tile-stats-values-limit.mbtiles.json @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "-135.000000,37.877021,-122.280579,40.979898", -"bounds": "-135.000000,37.877021,-122.280579,40.979898", +"antimeridian_adjusted_bounds": "-135.000000,37.874853,-122.280579,40.979898", +"bounds": "-135.000000,37.874853,-122.280579,40.979898", "center": "-122.299805,37.892187,12", "description": "tests/join-population/tabblock_06001420.mbtiles", "format": "pbf", diff --git a/tests/join-population/joined.mbtiles.json b/tests/join-population/joined.mbtiles.json index c1232739..acd23ed2 100644 --- a/tests/join-population/joined.mbtiles.json +++ b/tests/join-population/joined.mbtiles.json @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "-135.000000,37.877021,-122.280579,40.979898", -"bounds": "-135.000000,37.877021,-122.280579,40.979898", +"antimeridian_adjusted_bounds": "-135.000000,37.874853,-122.280579,40.979898", +"bounds": "-135.000000,37.874853,-122.280579,40.979898", "center": "-122.299805,37.892187,12", "description": "tests/join-population/tabblock_06001420.mbtiles", "format": "pbf", diff --git a/tests/join-population/macarthur-6-9-exclude.mbtiles.json b/tests/join-population/macarthur-6-9-exclude.mbtiles.json index 3f3f0eea..f079ea24 100644 --- a/tests/join-population/macarthur-6-9-exclude.mbtiles.json +++ b/tests/join-population/macarthur-6-9-exclude.mbtiles.json @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "-122.294655,37.695231,-122.104111,37.833107", -"bounds": "-122.294655,37.695231,-122.104111,37.833107", +"antimeridian_adjusted_bounds": "-122.294655,37.695231,-122.103424,37.833107", +"bounds": "-122.294655,37.695231,-122.103424,37.833107", "center": "-122.167969,37.833010,9", "description": "tests/join-population/macarthur.mbtiles", "format": "pbf", diff --git a/tests/join-population/macarthur-6-9.mbtiles.json b/tests/join-population/macarthur-6-9.mbtiles.json index 944a79d9..0452f638 100644 --- a/tests/join-population/macarthur-6-9.mbtiles.json +++ b/tests/join-population/macarthur-6-9.mbtiles.json @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "-122.294655,37.695231,-122.104111,37.833107", -"bounds": "-122.294655,37.695231,-122.104111,37.833107", +"antimeridian_adjusted_bounds": "-122.294655,37.695231,-122.103424,37.833107", +"bounds": "-122.294655,37.695231,-122.103424,37.833107", "center": "-122.167969,37.833010,9", "description": "tests/join-population/macarthur.mbtiles", "format": "pbf", diff --git a/tests/join-population/no-macarthur.mbtiles.json b/tests/join-population/no-macarthur.mbtiles.json index 6ffa3601..db2e7c61 100644 --- a/tests/join-population/no-macarthur.mbtiles.json +++ b/tests/join-population/no-macarthur.mbtiles.json @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "-135.000000,37.877021,-122.280579,40.979898", -"bounds": "-135.000000,37.877021,-122.280579,40.979898", +"antimeridian_adjusted_bounds": "-135.000000,37.874853,-122.280579,40.979898", +"bounds": "-135.000000,37.874853,-122.280579,40.979898", "center": "-122.299805,37.892187,12", "description": "tests/join-population/tabblock_06001420.mbtiles", "format": "pbf", diff --git a/tests/join-population/renamed.mbtiles.json b/tests/join-population/renamed.mbtiles.json index 727b83ff..58bcc153 100644 --- a/tests/join-population/renamed.mbtiles.json +++ b/tests/join-population/renamed.mbtiles.json @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "-122.294655,37.695231,-122.103424,37.833107", -"bounds": "-122.294655,37.695231,-122.103424,37.833107", +"antimeridian_adjusted_bounds": "-122.294655,37.694688,-122.103424,37.833649", +"bounds": "-122.294655,37.694688,-122.103424,37.833649", "center": "-122.167969,37.828608,10", "description": "tests/join-population/macarthur2.mbtiles", "format": "pbf", diff --git a/tests/join-population/windows.mbtiles.json b/tests/join-population/windows.mbtiles.json index b095f46a..570b1710 100644 --- a/tests/join-population/windows.mbtiles.json +++ b/tests/join-population/windows.mbtiles.json @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "-122.294655,37.695231,-122.103424,37.833107", -"bounds": "-122.294655,37.695231,-122.103424,37.833107", +"antimeridian_adjusted_bounds": "-122.294655,37.694688,-122.103424,37.833649", +"bounds": "-122.294655,37.694688,-122.103424,37.833649", "center": "-122.167969,37.833010,10", "description": "tests/join-population/macarthur.mbtiles", "format": "pbf", diff --git a/tile-join.cpp b/tile-join.cpp index f8c5552e..18a1c68f 100644 --- a/tile-join.cpp +++ b/tile-join.cpp @@ -1012,6 +1012,10 @@ void dispatch_tasks(std::map> &tasks, std::vector< for (size_t i = 0; i < CPUS; i++) { void *retval; + if (pthread_join(pthreads[i], &retval) != 0) { + perror("pthread_join"); + } + *minlat = std::min(*minlat, args[i].minlat); *minlon = std::min(*minlon, args[i].minlon); *maxlat = std::max(*maxlat, args[i].maxlat); @@ -1019,10 +1023,6 @@ void dispatch_tasks(std::map> &tasks, std::vector< *minlon2 = std::min(*minlon2, args[i].minlon2); *maxlon2 = std::max(*maxlon2, args[i].maxlon2); - if (pthread_join(pthreads[i], &retval) != 0) { - perror("pthread_join"); - } - for (auto ai = args[i].outputs.begin(); ai != args[i].outputs.end(); ++ai) { if (outdb != NULL) { mbtiles_write_tile(outdb, ai->first.z, ai->first.x, ai->first.y, ai->second.data(), ai->second.size()); From 632d031caa34ebee60350868ca7b021ff45dc89d Mon Sep 17 00:00:00 2001 From: Erica Fischer Date: Fri, 20 Dec 2024 09:46:15 -0800 Subject: [PATCH 17/29] Missed a test --- tests/raw-tiles/raw-tiles-z67-join.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/raw-tiles/raw-tiles-z67-join.json b/tests/raw-tiles/raw-tiles-z67-join.json index d82e92ee..8582128c 100644 --- a/tests/raw-tiles/raw-tiles-z67-join.json +++ b/tests/raw-tiles/raw-tiles-z67-join.json @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "-123.750000,45.089036,-120.937500,47.040182", -"bounds": "-123.750000,45.089036,-120.937500,47.040182", +"antimeridian_adjusted_bounds": "-122.682953,45.512121,-122.654800,45.569832", +"bounds": "-122.682953,45.512121,-122.654800,45.569832", "center": "-122.662354,45.514045,7", "description": "tests/raw-tiles/raw-tiles", "format": "pbf", From b87d8e45600af12ab822f8cdbe521456049ec3c6 Mon Sep 17 00:00:00 2001 From: Erica Fischer Date: Fri, 20 Dec 2024 10:41:53 -0800 Subject: [PATCH 18/29] Fix accidental inclusion of the top left of the tile in the bbox --- .../feature-filter/out/filtered.json.standard | 4 ++-- tests/join-population/joined-i.mbtiles.json | 4 ++-- .../joined-no-tile-stats.mbtiles.json | 4 ++-- .../join-population/joined-null.mbtiles.json | 4 ++-- ...d-tile-stats-attributes-limit.mbtiles.json | 4 ++-- ...ile-stats-sample-values-limit.mbtiles.json | 4 ++-- ...oined-tile-stats-values-limit.mbtiles.json | 4 ++-- tests/join-population/joined.mbtiles.json | 4 ++-- .../merged-folder.mbtiles.json | 4 ++-- tests/join-population/merged.mbtiles.json | 4 ++-- .../join-population/no-macarthur.mbtiles.json | 4 ++-- tests/join-population/raw-merged-folder.json | 4 ++-- tile-join.cpp | 20 ++++++++++--------- 13 files changed, 35 insertions(+), 33 deletions(-) diff --git a/tests/feature-filter/out/filtered.json.standard b/tests/feature-filter/out/filtered.json.standard index 4d9ae0dd..c391431c 100644 --- a/tests/feature-filter/out/filtered.json.standard +++ b/tests/feature-filter/out/filtered.json.standard @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "-180.000000,0.000000,0.966797,85.051129", -"bounds": "-180.000000,0.000000,0.966797,85.051129", +"antimeridian_adjusted_bounds": "-100.019531,0.000000,0.966797,0.966751", +"bounds": "-100.019531,0.000000,0.966797,0.966751", "center": "0.000000,0.000000,0", "description": "tests/feature-filter/out/all.mbtiles", "format": "pbf", diff --git a/tests/join-population/joined-i.mbtiles.json b/tests/join-population/joined-i.mbtiles.json index e50d42e9..6d8dd763 100644 --- a/tests/join-population/joined-i.mbtiles.json +++ b/tests/join-population/joined-i.mbtiles.json @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "-135.000000,37.881357,-122.280579,40.979898", -"bounds": "-135.000000,37.881357,-122.280579,40.979898", +"antimeridian_adjusted_bounds": "-122.309418,37.881357,-122.280579,37.900865", +"bounds": "-122.309418,37.881357,-122.280579,37.900865", "center": "-122.299805,37.892187,12", "description": "tests/join-population/tabblock_06001420.mbtiles", "format": "pbf", diff --git a/tests/join-population/joined-no-tile-stats.mbtiles.json b/tests/join-population/joined-no-tile-stats.mbtiles.json index 92a87d2e..4e5b7ec0 100644 --- a/tests/join-population/joined-no-tile-stats.mbtiles.json +++ b/tests/join-population/joined-no-tile-stats.mbtiles.json @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "-135.000000,37.874853,-122.280579,40.979898", -"bounds": "-135.000000,37.874853,-122.280579,40.979898", +"antimeridian_adjusted_bounds": "-122.343750,37.874853,-122.280579,37.900865", +"bounds": "-122.343750,37.874853,-122.280579,37.900865", "center": "-122.299805,37.892187,12", "description": "tests/join-population/tabblock_06001420.mbtiles", "format": "pbf", diff --git a/tests/join-population/joined-null.mbtiles.json b/tests/join-population/joined-null.mbtiles.json index 3d2a912e..c8a71676 100644 --- a/tests/join-population/joined-null.mbtiles.json +++ b/tests/join-population/joined-null.mbtiles.json @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "-135.000000,37.874853,-122.280579,40.979898", -"bounds": "-135.000000,37.874853,-122.280579,40.979898", +"antimeridian_adjusted_bounds": "-122.343750,37.874853,-122.280579,37.900865", +"bounds": "-122.343750,37.874853,-122.280579,37.900865", "center": "-122.299805,37.892187,12", "description": "tests/join-population/tabblock_06001420.mbtiles", "format": "pbf", diff --git a/tests/join-population/joined-tile-stats-attributes-limit.mbtiles.json b/tests/join-population/joined-tile-stats-attributes-limit.mbtiles.json index 7afa4f11..c6f6d52c 100644 --- a/tests/join-population/joined-tile-stats-attributes-limit.mbtiles.json +++ b/tests/join-population/joined-tile-stats-attributes-limit.mbtiles.json @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "-135.000000,37.874853,-122.280579,40.979898", -"bounds": "-135.000000,37.874853,-122.280579,40.979898", +"antimeridian_adjusted_bounds": "-122.343750,37.874853,-122.280579,37.900865", +"bounds": "-122.343750,37.874853,-122.280579,37.900865", "center": "-122.299805,37.892187,12", "description": "tests/join-population/tabblock_06001420.mbtiles", "format": "pbf", diff --git a/tests/join-population/joined-tile-stats-sample-values-limit.mbtiles.json b/tests/join-population/joined-tile-stats-sample-values-limit.mbtiles.json index b0b4c3f8..b3ce3787 100644 --- a/tests/join-population/joined-tile-stats-sample-values-limit.mbtiles.json +++ b/tests/join-population/joined-tile-stats-sample-values-limit.mbtiles.json @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "-135.000000,37.874853,-122.280579,40.979898", -"bounds": "-135.000000,37.874853,-122.280579,40.979898", +"antimeridian_adjusted_bounds": "-122.343750,37.874853,-122.280579,37.900865", +"bounds": "-122.343750,37.874853,-122.280579,37.900865", "center": "-122.299805,37.892187,12", "description": "tests/join-population/tabblock_06001420.mbtiles", "format": "pbf", diff --git a/tests/join-population/joined-tile-stats-values-limit.mbtiles.json b/tests/join-population/joined-tile-stats-values-limit.mbtiles.json index 517f1f24..7999ec3c 100644 --- a/tests/join-population/joined-tile-stats-values-limit.mbtiles.json +++ b/tests/join-population/joined-tile-stats-values-limit.mbtiles.json @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "-135.000000,37.874853,-122.280579,40.979898", -"bounds": "-135.000000,37.874853,-122.280579,40.979898", +"antimeridian_adjusted_bounds": "-122.343750,37.874853,-122.280579,37.900865", +"bounds": "-122.343750,37.874853,-122.280579,37.900865", "center": "-122.299805,37.892187,12", "description": "tests/join-population/tabblock_06001420.mbtiles", "format": "pbf", diff --git a/tests/join-population/joined.mbtiles.json b/tests/join-population/joined.mbtiles.json index acd23ed2..4d416618 100644 --- a/tests/join-population/joined.mbtiles.json +++ b/tests/join-population/joined.mbtiles.json @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "-135.000000,37.874853,-122.280579,40.979898", -"bounds": "-135.000000,37.874853,-122.280579,40.979898", +"antimeridian_adjusted_bounds": "-122.343750,37.874853,-122.280579,37.900865", +"bounds": "-122.343750,37.874853,-122.280579,37.900865", "center": "-122.299805,37.892187,12", "description": "tests/join-population/tabblock_06001420.mbtiles", "format": "pbf", diff --git a/tests/join-population/merged-folder.mbtiles.json b/tests/join-population/merged-folder.mbtiles.json index c0ae4f27..3e5f1cd4 100644 --- a/tests/join-population/merged-folder.mbtiles.json +++ b/tests/join-population/merged-folder.mbtiles.json @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "-135.000000,37.694688,-122.103424,40.979898", -"bounds": "-135.000000,37.694688,-122.103424,40.979898", +"antimeridian_adjusted_bounds": "-122.343750,37.694688,-122.103424,37.900865", +"bounds": "-122.343750,37.694688,-122.103424,37.900865", "center": "-122.299805,37.892187,12", "description": "tests/join-population/tabblock_06001420-folder", "format": "pbf", diff --git a/tests/join-population/merged.mbtiles.json b/tests/join-population/merged.mbtiles.json index e7ccaf9c..17d42609 100644 --- a/tests/join-population/merged.mbtiles.json +++ b/tests/join-population/merged.mbtiles.json @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "-135.000000,37.694688,-122.103424,40.979898", -"bounds": "-135.000000,37.694688,-122.103424,40.979898", +"antimeridian_adjusted_bounds": "-122.343750,37.694688,-122.103424,37.900865", +"bounds": "-122.343750,37.694688,-122.103424,37.900865", "center": "-122.299805,37.892187,12", "description": "tests/join-population/tabblock_06001420.mbtiles", "format": "pbf", diff --git a/tests/join-population/no-macarthur.mbtiles.json b/tests/join-population/no-macarthur.mbtiles.json index db2e7c61..5d33240a 100644 --- a/tests/join-population/no-macarthur.mbtiles.json +++ b/tests/join-population/no-macarthur.mbtiles.json @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "-135.000000,37.874853,-122.280579,40.979898", -"bounds": "-135.000000,37.874853,-122.280579,40.979898", +"antimeridian_adjusted_bounds": "-122.343750,37.874853,-122.280579,37.900865", +"bounds": "-122.343750,37.874853,-122.280579,37.900865", "center": "-122.299805,37.892187,12", "description": "tests/join-population/tabblock_06001420.mbtiles", "format": "pbf", diff --git a/tests/join-population/raw-merged-folder.json b/tests/join-population/raw-merged-folder.json index d0782293..a61b2cee 100644 --- a/tests/join-population/raw-merged-folder.json +++ b/tests/join-population/raw-merged-folder.json @@ -1,6 +1,6 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "-135.000000,37.694688,-122.103424,40.979898", -"bounds": "-135.000000,37.694688,-122.103424,40.979898", +"antimeridian_adjusted_bounds": "-122.343750,37.694688,-122.103424,37.900865", +"bounds": "-122.343750,37.694688,-122.103424,37.900865", "center": "-122.299805,37.892187,12", "description": "tests/join-population/tabblock_06001420.mbtiles", "format": "pbf", diff --git a/tile-join.cpp b/tile-join.cpp index 18a1c68f..8304ec0f 100644 --- a/tile-join.cpp +++ b/tile-join.cpp @@ -411,15 +411,17 @@ void append_tile(std::string message, int z, unsigned x, unsigned y, std::map Date: Fri, 3 Jan 2025 10:26:54 -0800 Subject: [PATCH 19/29] Case smashing and prefix trimming in the select --- tile-join.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tile-join.cpp b/tile-join.cpp index 8304ec0f..a9b2eea7 100644 --- a/tile-join.cpp +++ b/tile-join.cpp @@ -83,8 +83,10 @@ std::vector> get_joined_rows(sqlite3 *db, const ret.resize(join_keys.size()); // double quotes for table and column identifiers - const char *s = sqlite3_mprintf("select \"%w\", * from \"%w\" where \"%w\" in (", - join_table_column.c_str(), join_table.c_str(), join_table_column.c_str()); + const char *s = sqlite3_mprintf("select LOWER(LTRIM(SUBSTR(\"%w\",1,LENGTH(\"%w\")-3),'0')||SUBSTR(\"%w\",-3,3)), * from \"%w\" where LOWER(LTRIM(SUBSTR(\"%w\",1,LENGTH(\"%w\")-3),'0')||SUBSTR(\"%w\",-3,3)) in (", + join_table_column.c_str(), join_table_column.c_str(), join_table_column.c_str(), + join_table.c_str(), + join_table_column.c_str(), join_table_column.c_str(), join_table_column.c_str()); std::string query = s; sqlite3_free((void *) s); From d2279bcb5c6b4371a641b12095a9bb5d53d6f058 Mon Sep 17 00:00:00 2001 From: Erica Fischer Date: Fri, 3 Jan 2025 10:31:58 -0800 Subject: [PATCH 20/29] Add test of sql join --- Makefile | 7 +++++++ tests/join-sql/bboxes.pmtiles | Bin 0 -> 35150 bytes tests/join-sql/countries.gpkg | Bin 0 -> 73728 bytes tests/join-sql/countries.pmtiles.json | 25 +++++++++++++++++++++++++ 4 files changed, 32 insertions(+) create mode 100644 tests/join-sql/bboxes.pmtiles create mode 100644 tests/join-sql/countries.gpkg create mode 100644 tests/join-sql/countries.pmtiles.json diff --git a/Makefile b/Makefile index 2f522d00..7de48863 100644 --- a/Makefile +++ b/Makefile @@ -578,6 +578,13 @@ join-test: tippecanoe tippecanoe-decode tile-join ./tippecanoe-decode -x generator tests/ne_110m_ocean/join/joined.mbtiles > tests/ne_110m_ocean/join/joined.mbtiles.json.check cmp tests/ne_110m_ocean/join/joined.mbtiles.json.check tests/ne_110m_ocean/join/joined.mbtiles.json rm -f tests/ne_110m_ocean/join/ocean.mbtiles tests/ne_110m_ocean/join/countries.mbtiles tests/ne_110m_ocean/join/joined.mbtiles tests/ne_110m_ocean/join/joined.mbtiles.json.check + # + # Test sql join + # + ./tile-join -i -f -o tests/join-sql/countries.pmtiles --join-sqlite tests/join-sql/countries.gpkg --join-table countries --join-tile-column ne10-admin0:name_en --join-table-column country tests/join-sql/bboxes.pmtiles + ./tippecanoe-decode -x generator tests/join-sql/countries.pmtiles > tests/join-sql/countries.pmtiles.json.check + cmp tests/join-sql/countries.pmtiles.json.check tests/join-sql/countries.pmtiles.json + rm -f tests/join-sql/countries.pmtiles tests/join-sql/countries.pmtiles.json.check accumulate-test: # there are 144 features with POP1950 in the original dataset diff --git a/tests/join-sql/bboxes.pmtiles b/tests/join-sql/bboxes.pmtiles new file mode 100644 index 0000000000000000000000000000000000000000..12c79e0fd9b28fc90694ab9a6131d1251bae4fe1 GIT binary patch literal 35150 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

C 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 b0zd!=00AHX1c1QnLSUHf9cP>0u;~3CAP~-U literal 0 HcmV?d00001 diff --git a/tests/join-sql/countries.pmtiles.json b/tests/join-sql/countries.pmtiles.json new file mode 100644 index 00000000..625e928d --- /dev/null +++ b/tests/join-sql/countries.pmtiles.json @@ -0,0 +1,25 @@ +{ "type": "FeatureCollection", "properties": { +"antimeridian_adjusted_bounds": "-61.797841,-21.370782,55.854503,55.065334", +"bounds": "-61.797841,-21.370782,55.854503,55.065334", +"center": "0.000000,0.000000,0", +"description": "/tmp/tmp588djzw6/bboxes.pmtiles", +"format": "pbf", +"generator_options": "tippecanoe '--tile-stats-values-limit=1' -o /tmp/tmp588djzw6/bboxes.pmtiles -l parsed-bboxes -U1 '--smallest-maximum-zoom-guess=7' -Bg --drop-fraction-as-needed -P --hilbert --generate-variable-depth-tile-pyramid -rp -b0 '--extend-zooms-if-still-dropping-maximum=3' '--extra-detail=30' -D10 '--tiny-polygon-size=1' --no-tiny-polygon-reduction-at-maximum-zoom '--simplification=1' '--simplification-at-maximum-zoom=0.25' --detect-longitude-wraparound --preserve-input-order '--maximum-tile-bytes=2621440' '--maximum-string-attribute-length=65536' --preserve-point-density-threshold 64 --set-attribute '{\"felt:cluster_size\": 1}' --accumulate-attribute '{\"felt:cluster_size\": \"sum\"}' --preserve-multiplier-density-threshold 512 '--accumulate-numeric-attributes=felt' /tmp/tmpg9jf8evz/parsed.geojsonseq.gz; ./tile-join -i -f -o tests/join-sql/countries.pmtiles --join-sqlite tests/join-sql/countries.gpkg --join-table countries --join-tile-column ne10-admin0:name_en --join-table-column country tests/join-sql/bboxes.pmtiles", +"json": "{\"vector_layers\":[{\"id\":\"parsed-bboxes\",\"description\":\"\",\"minzoom\":0,\"maxzoom\":0,\"fields\":{\"another\":\"String\",\"country\":\"String\",\"felt:cluster_size\":\"Number\",\"fid\":\"Number\",\"ne10-admin0:iso_a2_eh\":\"String\",\"ne10-admin0:iso_a3_eh\":\"String\",\"ne10-admin0:name_en\":\"String\",\"something\":\"String\"}}],\"tilestats\":{\"layerCount\":1,\"layers\":[{\"layer\":\"parsed-bboxes\",\"count\":3,\"geometry\":\"Polygon\",\"attributeCount\":8,\"attributes\":[{\"attribute\":\"another\",\"count\":3,\"type\":\"string\",\"values\":[\"bar\",\"why\",\"yes\"]},{\"attribute\":\"country\",\"count\":3,\"type\":\"string\",\"values\":[\"France\",\"Germany\",\"Italy\"]},{\"attribute\":\"felt:cluster_size\",\"count\":1,\"type\":\"number\",\"values\":[1],\"min\":1,\"max\":1},{\"attribute\":\"fid\",\"count\":3,\"type\":\"number\",\"values\":[1,2,3],\"min\":1,\"max\":3},{\"attribute\":\"ne10-admin0:iso_a2_eh\",\"count\":3,\"type\":\"string\",\"values\":[\"de\",\"fr\",\"it\"]},{\"attribute\":\"ne10-admin0:iso_a3_eh\",\"count\":3,\"type\":\"string\",\"values\":[\"deu\",\"fra\",\"ita\"]},{\"attribute\":\"ne10-admin0:name_en\",\"count\":3,\"type\":\"string\",\"values\":[\"france\",\"germany\",\"italy\"]},{\"attribute\":\"something\",\"count\":3,\"type\":\"string\",\"values\":[\"blah\",\"foo\",\"what\"]}]}]}}", +"maxzoom": "0", +"minzoom": "0", +"name": "/tmp/tmp588djzw6/bboxes.pmtiles", +"strategies": "[{\"truncated_zooms\":1}]", +"type": "overlay", +"version": "2" +}, "features": [ +{ "type": "FeatureCollection", "properties": { "zoom": 0, "x": 0, "y": 0 }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "parsed-bboxes", "version": 2, "extent": 1073741824 }, "features": [ +{ "type": "Feature", "id": 322, "properties": { "ne10-admin0:name_en": "germany", "another": "yes", "country": "Germany", "fid": 1, "something": "blah", "ne10-admin0:iso_a2_eh": "de", "ne10-admin0:iso_a3_eh": "deu", "felt:cluster_size": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 15.022060, 47.271121 ], [ 5.852490, 47.271121 ], [ 5.852490, 55.065334 ], [ 15.022060, 55.065334 ], [ 15.022060, 47.271121 ] ] ] } } +, +{ "type": "Feature", "id": 435, "properties": { "ne10-admin0:name_en": "france", "another": "why", "country": "France", "fid": 2, "something": "what", "ne10-admin0:iso_a2_eh": "fr", "ne10-admin0:iso_a3_eh": "fra", "felt:cluster_size": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -61.797841, -21.370782 ], [ -61.797841, 51.087541 ], [ 55.854503, 51.087541 ], [ 55.854503, -21.370782 ], [ -61.797841, -21.370782 ] ] ] } } +, +{ "type": "Feature", "id": 650, "properties": { "ne10-admin0:name_en": "italy", "another": "bar", "country": "Italy", "fid": 3, "something": "foo", "ne10-admin0:iso_a2_eh": "it", "ne10-admin0:iso_a3_eh": "ita", "felt:cluster_size": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 18.517426, 35.489244 ], [ 6.602728, 35.489244 ], [ 6.602728, 47.085215 ], [ 18.517426, 47.085215 ], [ 18.517426, 35.489244 ] ] ] } } +] } +] } +] } From 2ad90812f65029dfdfc86b345565c9f2b7ccdfb6 Mon Sep 17 00:00:00 2001 From: Erica Fischer Date: Mon, 6 Jan 2025 13:21:36 -0800 Subject: [PATCH 21/29] Make the join column option a join expression option --- Makefile | 2 +- tests/join-sql/countries.pmtiles.json | 2 +- tile-join.cpp | 24 +++++++++++------------- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/Makefile b/Makefile index 7de48863..30743475 100644 --- a/Makefile +++ b/Makefile @@ -581,7 +581,7 @@ join-test: tippecanoe tippecanoe-decode tile-join # # Test sql join # - ./tile-join -i -f -o tests/join-sql/countries.pmtiles --join-sqlite tests/join-sql/countries.gpkg --join-table countries --join-tile-column ne10-admin0:name_en --join-table-column country tests/join-sql/bboxes.pmtiles + ./tile-join -i -f -o tests/join-sql/countries.pmtiles --join-sqlite tests/join-sql/countries.gpkg --join-table countries --join-tile-attribute ne10-admin0:name_en --join-table-expression 'lower(country)' tests/join-sql/bboxes.pmtiles ./tippecanoe-decode -x generator tests/join-sql/countries.pmtiles > tests/join-sql/countries.pmtiles.json.check cmp tests/join-sql/countries.pmtiles.json.check tests/join-sql/countries.pmtiles.json rm -f tests/join-sql/countries.pmtiles tests/join-sql/countries.pmtiles.json.check diff --git a/tests/join-sql/countries.pmtiles.json b/tests/join-sql/countries.pmtiles.json index 625e928d..9581da99 100644 --- a/tests/join-sql/countries.pmtiles.json +++ b/tests/join-sql/countries.pmtiles.json @@ -4,7 +4,7 @@ "center": "0.000000,0.000000,0", "description": "/tmp/tmp588djzw6/bboxes.pmtiles", "format": "pbf", -"generator_options": "tippecanoe '--tile-stats-values-limit=1' -o /tmp/tmp588djzw6/bboxes.pmtiles -l parsed-bboxes -U1 '--smallest-maximum-zoom-guess=7' -Bg --drop-fraction-as-needed -P --hilbert --generate-variable-depth-tile-pyramid -rp -b0 '--extend-zooms-if-still-dropping-maximum=3' '--extra-detail=30' -D10 '--tiny-polygon-size=1' --no-tiny-polygon-reduction-at-maximum-zoom '--simplification=1' '--simplification-at-maximum-zoom=0.25' --detect-longitude-wraparound --preserve-input-order '--maximum-tile-bytes=2621440' '--maximum-string-attribute-length=65536' --preserve-point-density-threshold 64 --set-attribute '{\"felt:cluster_size\": 1}' --accumulate-attribute '{\"felt:cluster_size\": \"sum\"}' --preserve-multiplier-density-threshold 512 '--accumulate-numeric-attributes=felt' /tmp/tmpg9jf8evz/parsed.geojsonseq.gz; ./tile-join -i -f -o tests/join-sql/countries.pmtiles --join-sqlite tests/join-sql/countries.gpkg --join-table countries --join-tile-column ne10-admin0:name_en --join-table-column country tests/join-sql/bboxes.pmtiles", +"generator_options": "tippecanoe '--tile-stats-values-limit=1' -o /tmp/tmp588djzw6/bboxes.pmtiles -l parsed-bboxes -U1 '--smallest-maximum-zoom-guess=7' -Bg --drop-fraction-as-needed -P --hilbert --generate-variable-depth-tile-pyramid -rp -b0 '--extend-zooms-if-still-dropping-maximum=3' '--extra-detail=30' -D10 '--tiny-polygon-size=1' --no-tiny-polygon-reduction-at-maximum-zoom '--simplification=1' '--simplification-at-maximum-zoom=0.25' --detect-longitude-wraparound --preserve-input-order '--maximum-tile-bytes=2621440' '--maximum-string-attribute-length=65536' --preserve-point-density-threshold 64 --set-attribute '{\"felt:cluster_size\": 1}' --accumulate-attribute '{\"felt:cluster_size\": \"sum\"}' --preserve-multiplier-density-threshold 512 '--accumulate-numeric-attributes=felt' /tmp/tmpg9jf8evz/parsed.geojsonseq.gz; ./tile-join -i -f -o tests/join-sql/countries.pmtiles --join-sqlite tests/join-sql/countries.gpkg --join-table countries --join-tile-attribute ne10-admin0:name_en --join-table-expression 'lower(country)' tests/join-sql/bboxes.pmtiles", "json": "{\"vector_layers\":[{\"id\":\"parsed-bboxes\",\"description\":\"\",\"minzoom\":0,\"maxzoom\":0,\"fields\":{\"another\":\"String\",\"country\":\"String\",\"felt:cluster_size\":\"Number\",\"fid\":\"Number\",\"ne10-admin0:iso_a2_eh\":\"String\",\"ne10-admin0:iso_a3_eh\":\"String\",\"ne10-admin0:name_en\":\"String\",\"something\":\"String\"}}],\"tilestats\":{\"layerCount\":1,\"layers\":[{\"layer\":\"parsed-bboxes\",\"count\":3,\"geometry\":\"Polygon\",\"attributeCount\":8,\"attributes\":[{\"attribute\":\"another\",\"count\":3,\"type\":\"string\",\"values\":[\"bar\",\"why\",\"yes\"]},{\"attribute\":\"country\",\"count\":3,\"type\":\"string\",\"values\":[\"France\",\"Germany\",\"Italy\"]},{\"attribute\":\"felt:cluster_size\",\"count\":1,\"type\":\"number\",\"values\":[1],\"min\":1,\"max\":1},{\"attribute\":\"fid\",\"count\":3,\"type\":\"number\",\"values\":[1,2,3],\"min\":1,\"max\":3},{\"attribute\":\"ne10-admin0:iso_a2_eh\",\"count\":3,\"type\":\"string\",\"values\":[\"de\",\"fr\",\"it\"]},{\"attribute\":\"ne10-admin0:iso_a3_eh\",\"count\":3,\"type\":\"string\",\"values\":[\"deu\",\"fra\",\"ita\"]},{\"attribute\":\"ne10-admin0:name_en\",\"count\":3,\"type\":\"string\",\"values\":[\"france\",\"germany\",\"italy\"]},{\"attribute\":\"something\",\"count\":3,\"type\":\"string\",\"values\":[\"blah\",\"foo\",\"what\"]}]}]}}", "maxzoom": "0", "minzoom": "0", diff --git a/tile-join.cpp b/tile-join.cpp index a9b2eea7..8589c5ad 100644 --- a/tile-join.cpp +++ b/tile-join.cpp @@ -57,8 +57,8 @@ std::map renames; bool exclude_all = false; bool exclude_all_tile_attributes = false; std::vector unidecode_data; -std::string join_tile_column; -std::string join_table_column; +std::string join_tile_attribute; +std::string join_table_expression; std::string join_table; std::string attribute_for_id; @@ -83,10 +83,8 @@ std::vector> get_joined_rows(sqlite3 *db, const ret.resize(join_keys.size()); // double quotes for table and column identifiers - const char *s = sqlite3_mprintf("select LOWER(LTRIM(SUBSTR(\"%w\",1,LENGTH(\"%w\")-3),'0')||SUBSTR(\"%w\",-3,3)), * from \"%w\" where LOWER(LTRIM(SUBSTR(\"%w\",1,LENGTH(\"%w\")-3),'0')||SUBSTR(\"%w\",-3,3)) in (", - join_table_column.c_str(), join_table_column.c_str(), join_table_column.c_str(), - join_table.c_str(), - join_table_column.c_str(), join_table_column.c_str(), join_table_column.c_str()); + const char *s = sqlite3_mprintf("select %s, * from \"%w\" where %s in (", + join_table_expression.c_str(), join_table.c_str(), join_table_expression.c_str()); std::string query = s; sqlite3_free((void *) s); @@ -258,7 +256,7 @@ void append_tile(std::string message, int z, unsigned x, unsigned y, std::map Date: Tue, 7 Jan 2025 11:34:48 -0800 Subject: [PATCH 22/29] Fix antimeridian adjustment. Z0 can't wait until the end of the tile --- tests/ne_110m_ocean/join/joined.mbtiles.json | 2 +- tile-join.cpp | 38 +++++++++++--------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/tests/ne_110m_ocean/join/joined.mbtiles.json b/tests/ne_110m_ocean/join/joined.mbtiles.json index 08148a95..4c2c3c80 100644 --- a/tests/ne_110m_ocean/join/joined.mbtiles.json +++ b/tests/ne_110m_ocean/join/joined.mbtiles.json @@ -1,5 +1,5 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "-180.000000,-85.051129,180.000000,85.051129", +"antimeridian_adjusted_bounds": "0.000000,-85.051129,359.912109,85.051129", "bounds": "-180.000000,-85.051129,180.000000,85.051129", "center": "-45.000000,33.256630,4", "description": "tests/ne_110m_ocean/join/ocean.mbtiles", diff --git a/tile-join.cpp b/tile-join.cpp index 8589c5ad..eb2b160a 100644 --- a/tile-join.cpp +++ b/tile-join.cpp @@ -273,6 +273,9 @@ void append_tile(std::string message, int z, unsigned x, unsigned y, std::mapmaxlat = std::max(a->maxlat, std::max(lat1, lat2)); a->maxlon = std::max(a->maxlon, std::max(lon1, lon2)); - if (lon1 < 0 || lon2 < 0) { - lon1 += 360; - lon2 += 360; - } + tile2lonlat(minx2, maxy, 32, &lon1, &lat1); + tile2lonlat(maxx2, miny, 32, &lon2, &lat2); a->minlon2 = std::min(a->minlon2, std::min(lon1, lon2)); a->maxlon2 = std::max(a->maxlon2, std::max(lon1, lon2)); From b0326b65412a624424962911055fa74d4731c58e Mon Sep 17 00:00:00 2001 From: Erica Fischer Date: Tue, 7 Jan 2025 13:22:36 -0800 Subject: [PATCH 23/29] Checkpoint on accepting multiple joined rows per tiled feature --- tile-join.cpp | 59 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/tile-join.cpp b/tile-join.cpp index eb2b160a..30e72f3b 100644 --- a/tile-join.cpp +++ b/tile-join.cpp @@ -60,6 +60,7 @@ std::vector unidecode_data; std::string join_tile_attribute; std::string join_table_expression; std::string join_table; +size_t join_count_limit = 1; std::string attribute_for_id; bool want_overzoom = false; @@ -78,8 +79,11 @@ struct stats { std::vector strategies{}; }; -std::vector> get_joined_rows(sqlite3 *db, const std::vector &join_keys) { - std::vector> ret; +// list, per feature in the tile, +// of lists of features in the sqlite response, +// each of which is a mapping from keys to values +std::vector>> get_joined_rows(sqlite3 *db, const std::vector &join_keys) { + std::vector>> ret; ret.resize(join_keys.size()); // double quotes for table and column identifiers @@ -109,6 +113,8 @@ std::vector> get_joined_rows(sqlite3 *db, const } } + // this doesn't add a LIMIT to the query because our limit + // is per tiled feature, not a limit on the entire query response. query += ");"; sqlite3_stmt *stmt; @@ -129,22 +135,24 @@ std::vector> get_joined_rows(sqlite3 *db, const continue; } - for (int i = 1; i < count; i++) { - int type = sqlite3_column_type(stmt, i); - mvt_value v; - v.type = mvt_null; + if (ret[f->second].size() < join_count_limit) { + for (int i = 1; i < count; i++) { + int type = sqlite3_column_type(stmt, i); + mvt_value v; + v.type = mvt_null; - if (type == SQLITE_INTEGER || type == SQLITE_FLOAT) { - v = mvt_value(sqlite3_column_double(stmt, i)); - } else if (type == SQLITE_TEXT || type == SQLITE_BLOB) { - v.set_string_value((const char *) sqlite3_column_text(stmt, i)); + if (type == SQLITE_INTEGER || type == SQLITE_FLOAT) { + v = mvt_value(sqlite3_column_double(stmt, i)); + } else if (type == SQLITE_TEXT || type == SQLITE_BLOB) { + v.set_string_value((const char *) sqlite3_column_text(stmt, i)); + } + + const char *name = sqlite3_column_name(stmt, i); + row.emplace(name, v); } - const char *name = sqlite3_column_name(stmt, i); - row.emplace(name, v); + ret[f->second].push_back(std::move(row)); } - - ret[f->second] = std::move(row); } } if (sqlite3_finalize(stmt) != SQLITE_OK) { @@ -243,7 +251,7 @@ void append_tile(std::string message, int z, unsigned x, unsigned y, std::map> joined; + std::vector>> joined; if (db != NULL) { // collect join keys for sql query @@ -318,14 +326,16 @@ void append_tile(std::string message, int z, unsigned x, unsigned y, std::map>(kv.first, std::pair(kv.second, mvt_value_to_serial_val(kv.second)))); - key_order.push_back(kv.first); + for (auto const &joined_feature : joined[f]) { + for (auto const &kv : joined_feature) { + if (kv.first == attribute_for_id) { + outfeature.has_id = true; + outfeature.id = mvt_value_to_long_long(kv.second); + } else if (include.count(kv.first) || (!exclude_all && exclude.count(kv.first) == 0 && exclude_attributes.count(kv.first) == 0)) { + if (kv.second.type != mvt_null) { + attributes.insert(std::pair>(kv.first, std::pair(kv.second, mvt_value_to_serial_val(kv.second)))); + key_order.push_back(kv.first); + } } } } @@ -1403,6 +1413,7 @@ int main(int argc, char **argv) { {"join-tile-attribute", required_argument, 0, '~'}, {"join-table-expression", required_argument, 0, '~'}, {"join-table", required_argument, 0, '~'}, + {"join-count-limit", required_argument, 0, '~'}, {"use-attribute-for-id", required_argument, 0, '~'}, {"no-tile-size-limit", no_argument, &pk, 1}, @@ -1605,6 +1616,8 @@ int main(int argc, char **argv) { attribute_for_id = optarg; } else if (strcmp(opt, "exclude-all-tile-attributes") == 0) { exclude_all_tile_attributes = true; + } else if (strcmp(opt, "join-count-limit") == 0) { + join_count_limit = atoi(optarg); } else { fprintf(stderr, "%s: Unrecognized option --%s\n", argv[0], opt); exit(EXIT_ARGS); From 3d22a41171985d0520c9020fc9156b3356d27d75 Mon Sep 17 00:00:00 2001 From: Erica Fischer Date: Tue, 7 Jan 2025 13:27:35 -0800 Subject: [PATCH 24/29] Adding the joined attribute should be per-feature, not per-attribute --- tile-join.cpp | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/tile-join.cpp b/tile-join.cpp index 30e72f3b..14c95e8d 100644 --- a/tile-join.cpp +++ b/tile-join.cpp @@ -305,6 +305,26 @@ void append_tile(std::string message, int z, unsigned x, unsigned y, std::map> attributes; std::vector key_order; + if (f < joined.size()) { + if (joined[f].size() > 0) { + matched = true; + } + + for (auto const &joined_feature : joined[f]) { + for (auto const &kv : joined_feature) { + if (kv.first == attribute_for_id) { + outfeature.has_id = true; + outfeature.id = mvt_value_to_long_long(kv.second); + } else if (include.count(kv.first) || (!exclude_all && exclude.count(kv.first) == 0 && exclude_attributes.count(kv.first) == 0)) { + if (kv.second.type != mvt_null) { + attributes.insert(std::pair>(kv.first, std::pair(kv.second, mvt_value_to_serial_val(kv.second)))); + key_order.push_back(kv.first); + } + } + } + } + } + for (size_t t = 0; t + 1 < feat.tags.size(); t += 2) { const std::string &key = layer.keys[feat.tags[t]]; mvt_value &val = layer.values[feat.tags[t + 1]]; @@ -321,26 +341,6 @@ void append_tile(std::string message, int z, unsigned x, unsigned y, std::map 0) { - matched = true; - } - - for (auto const &joined_feature : joined[f]) { - for (auto const &kv : joined_feature) { - if (kv.first == attribute_for_id) { - outfeature.has_id = true; - outfeature.id = mvt_value_to_long_long(kv.second); - } else if (include.count(kv.first) || (!exclude_all && exclude.count(kv.first) == 0 && exclude_attributes.count(kv.first) == 0)) { - if (kv.second.type != mvt_null) { - attributes.insert(std::pair>(kv.first, std::pair(kv.second, mvt_value_to_serial_val(kv.second)))); - key_order.push_back(kv.first); - } - } - } - } - } - if (header.size() > 0 && key == header[0]) { std::map>::iterator ii = mapping.find(sv.s); From 504babf8424e26a99b1f603a7469968d580ce2b0 Mon Sep 17 00:00:00 2001 From: Erica Fischer Date: Tue, 7 Jan 2025 13:35:08 -0800 Subject: [PATCH 25/29] Forgot to update the test. Order of joined attributes has changed. --- tests/join-sql/countries.pmtiles.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/join-sql/countries.pmtiles.json b/tests/join-sql/countries.pmtiles.json index 9581da99..f2cce016 100644 --- a/tests/join-sql/countries.pmtiles.json +++ b/tests/join-sql/countries.pmtiles.json @@ -15,11 +15,11 @@ }, "features": [ { "type": "FeatureCollection", "properties": { "zoom": 0, "x": 0, "y": 0 }, "features": [ { "type": "FeatureCollection", "properties": { "layer": "parsed-bboxes", "version": 2, "extent": 1073741824 }, "features": [ -{ "type": "Feature", "id": 322, "properties": { "ne10-admin0:name_en": "germany", "another": "yes", "country": "Germany", "fid": 1, "something": "blah", "ne10-admin0:iso_a2_eh": "de", "ne10-admin0:iso_a3_eh": "deu", "felt:cluster_size": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 15.022060, 47.271121 ], [ 5.852490, 47.271121 ], [ 5.852490, 55.065334 ], [ 15.022060, 55.065334 ], [ 15.022060, 47.271121 ] ] ] } } +{ "type": "Feature", "id": 322, "properties": { "another": "yes", "country": "Germany", "fid": 1, "something": "blah", "ne10-admin0:name_en": "germany", "ne10-admin0:iso_a2_eh": "de", "ne10-admin0:iso_a3_eh": "deu", "felt:cluster_size": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 15.022060, 47.271121 ], [ 5.852490, 47.271121 ], [ 5.852490, 55.065334 ], [ 15.022060, 55.065334 ], [ 15.022060, 47.271121 ] ] ] } } , -{ "type": "Feature", "id": 435, "properties": { "ne10-admin0:name_en": "france", "another": "why", "country": "France", "fid": 2, "something": "what", "ne10-admin0:iso_a2_eh": "fr", "ne10-admin0:iso_a3_eh": "fra", "felt:cluster_size": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -61.797841, -21.370782 ], [ -61.797841, 51.087541 ], [ 55.854503, 51.087541 ], [ 55.854503, -21.370782 ], [ -61.797841, -21.370782 ] ] ] } } +{ "type": "Feature", "id": 435, "properties": { "another": "why", "country": "France", "fid": 2, "something": "what", "ne10-admin0:name_en": "france", "ne10-admin0:iso_a2_eh": "fr", "ne10-admin0:iso_a3_eh": "fra", "felt:cluster_size": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -61.797841, -21.370782 ], [ -61.797841, 51.087541 ], [ 55.854503, 51.087541 ], [ 55.854503, -21.370782 ], [ -61.797841, -21.370782 ] ] ] } } , -{ "type": "Feature", "id": 650, "properties": { "ne10-admin0:name_en": "italy", "another": "bar", "country": "Italy", "fid": 3, "something": "foo", "ne10-admin0:iso_a2_eh": "it", "ne10-admin0:iso_a3_eh": "ita", "felt:cluster_size": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 18.517426, 35.489244 ], [ 6.602728, 35.489244 ], [ 6.602728, 47.085215 ], [ 18.517426, 47.085215 ], [ 18.517426, 35.489244 ] ] ] } } +{ "type": "Feature", "id": 650, "properties": { "another": "bar", "country": "Italy", "fid": 3, "something": "foo", "ne10-admin0:name_en": "italy", "ne10-admin0:iso_a2_eh": "it", "ne10-admin0:iso_a3_eh": "ita", "felt:cluster_size": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 18.517426, 35.489244 ], [ 6.602728, 35.489244 ], [ 6.602728, 47.085215 ], [ 18.517426, 47.085215 ], [ 18.517426, 35.489244 ] ] ] } } ] } ] } ] } From 6b9a1286c247484b3d94a1e1116abc3c324c98a9 Mon Sep 17 00:00:00 2001 From: Erica Fischer Date: Tue, 7 Jan 2025 16:38:07 -0800 Subject: [PATCH 26/29] Get the attributes back in the right order --- tests/join-sql/countries.pmtiles.json | 6 +- tile-join.cpp | 198 +++++++++++++++++--------- 2 files changed, 134 insertions(+), 70 deletions(-) diff --git a/tests/join-sql/countries.pmtiles.json b/tests/join-sql/countries.pmtiles.json index f2cce016..de215f8d 100644 --- a/tests/join-sql/countries.pmtiles.json +++ b/tests/join-sql/countries.pmtiles.json @@ -15,11 +15,11 @@ }, "features": [ { "type": "FeatureCollection", "properties": { "zoom": 0, "x": 0, "y": 0 }, "features": [ { "type": "FeatureCollection", "properties": { "layer": "parsed-bboxes", "version": 2, "extent": 1073741824 }, "features": [ -{ "type": "Feature", "id": 322, "properties": { "another": "yes", "country": "Germany", "fid": 1, "something": "blah", "ne10-admin0:name_en": "germany", "ne10-admin0:iso_a2_eh": "de", "ne10-admin0:iso_a3_eh": "deu", "felt:cluster_size": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 15.022060, 47.271121 ], [ 5.852490, 47.271121 ], [ 5.852490, 55.065334 ], [ 15.022060, 55.065334 ], [ 15.022060, 47.271121 ] ] ] } } +{ "type": "Feature", "id": 322, "properties": { "ne10-admin0:name_en": "germany", "ne10-admin0:iso_a2_eh": "de", "ne10-admin0:iso_a3_eh": "deu", "felt:cluster_size": 1, "another": "yes", "country": "Germany", "fid": 1, "something": "blah" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 15.022060, 47.271121 ], [ 5.852490, 47.271121 ], [ 5.852490, 55.065334 ], [ 15.022060, 55.065334 ], [ 15.022060, 47.271121 ] ] ] } } , -{ "type": "Feature", "id": 435, "properties": { "another": "why", "country": "France", "fid": 2, "something": "what", "ne10-admin0:name_en": "france", "ne10-admin0:iso_a2_eh": "fr", "ne10-admin0:iso_a3_eh": "fra", "felt:cluster_size": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -61.797841, -21.370782 ], [ -61.797841, 51.087541 ], [ 55.854503, 51.087541 ], [ 55.854503, -21.370782 ], [ -61.797841, -21.370782 ] ] ] } } +{ "type": "Feature", "id": 435, "properties": { "ne10-admin0:name_en": "france", "ne10-admin0:iso_a2_eh": "fr", "ne10-admin0:iso_a3_eh": "fra", "felt:cluster_size": 1, "another": "why", "country": "France", "fid": 2, "something": "what" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -61.797841, -21.370782 ], [ -61.797841, 51.087541 ], [ 55.854503, 51.087541 ], [ 55.854503, -21.370782 ], [ -61.797841, -21.370782 ] ] ] } } , -{ "type": "Feature", "id": 650, "properties": { "another": "bar", "country": "Italy", "fid": 3, "something": "foo", "ne10-admin0:name_en": "italy", "ne10-admin0:iso_a2_eh": "it", "ne10-admin0:iso_a3_eh": "ita", "felt:cluster_size": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 18.517426, 35.489244 ], [ 6.602728, 35.489244 ], [ 6.602728, 47.085215 ], [ 18.517426, 47.085215 ], [ 18.517426, 35.489244 ] ] ] } } +{ "type": "Feature", "id": 650, "properties": { "ne10-admin0:name_en": "italy", "ne10-admin0:iso_a2_eh": "it", "ne10-admin0:iso_a3_eh": "ita", "felt:cluster_size": 1, "another": "bar", "country": "Italy", "fid": 3, "something": "foo" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 18.517426, 35.489244 ], [ 6.602728, 35.489244 ], [ 6.602728, 47.085215 ], [ 18.517426, 47.085215 ], [ 18.517426, 35.489244 ] ] ] } } ] } ] } ] } diff --git a/tile-join.cpp b/tile-join.cpp index 14c95e8d..a0c75faf 100644 --- a/tile-join.cpp +++ b/tile-join.cpp @@ -294,16 +294,17 @@ void append_tile(std::string message, int z, unsigned x, unsigned y, std::map> attributes; + std::vector key_order; + }; - if (feat.has_id) { - outfeature.has_id = true; - outfeature.id = feat.id; - } + std::vector matches; + bool matched = false; - std::map> attributes; - std::vector key_order; + // start filling out sql matches if (f < joined.size()) { if (joined[f].size() > 0) { @@ -311,90 +312,149 @@ void append_tile(std::string message, int z, unsigned x, unsigned y, std::map>(key, std::pair(val, sv))); + m.key_order.push_back(key); + } + } + } + for (auto const &kv : joined_feature) { if (kv.first == attribute_for_id) { - outfeature.has_id = true; - outfeature.id = mvt_value_to_long_long(kv.second); + m.has_id = true; + m.id = mvt_value_to_long_long(kv.second); } else if (include.count(kv.first) || (!exclude_all && exclude.count(kv.first) == 0 && exclude_attributes.count(kv.first) == 0)) { if (kv.second.type != mvt_null) { - attributes.insert(std::pair>(kv.first, std::pair(kv.second, mvt_value_to_serial_val(kv.second)))); - key_order.push_back(kv.first); + m.attributes.insert(std::pair>(kv.first, std::pair(kv.second, mvt_value_to_serial_val(kv.second)))); + m.key_order.push_back(kv.first); } } } + + matches.push_back(m); } } - for (size_t t = 0; t + 1 < feat.tags.size(); t += 2) { - const std::string &key = layer.keys[feat.tags[t]]; - mvt_value &val = layer.values[feat.tags[t + 1]]; - serial_val sv = mvt_value_to_serial_val(val); + // look for csv matches and start filling them out - if (sv.type == mvt_null) { - continue; - } + if (!matched) { + match m; + m.id = feat.id; + m.has_id = feat.has_id; + // populate attributes and key_order as we look for matches, + // because apparently at some point i thought it was important + // to insert the joined attributes at the point in the sequence + // where the join key had been - if (!exclude_all_tile_attributes) { - if (include.count(key) || (!exclude_all && exclude.count(key) == 0 && exclude_attributes.count(key) == 0)) { - attributes.insert(std::pair>(key, std::pair(val, sv))); - key_order.push_back(key); + for (size_t t = 0; t + 1 < feat.tags.size(); t += 2) { + const std::string &key = layer.keys[feat.tags[t]]; + mvt_value &val = layer.values[feat.tags[t + 1]]; + serial_val sv = mvt_value_to_serial_val(val); + + if (val.type == mvt_null) { + continue; + } + + if (!exclude_all_tile_attributes) { + if (include.count(std::string(key)) || (!exclude_all && exclude.count(std::string(key)) == 0 && exclude_attributes.count(std::string(key)) == 0)) { + m.attributes.insert(std::pair>(key, std::pair(val, sv))); + m.key_order.push_back(key); + } } - } - if (header.size() > 0 && key == header[0]) { - std::map>::iterator ii = mapping.find(sv.s); + if (header.size() > 0 && key == header[0]) { + std::map>::iterator ii = mapping.find(sv.s); + + if (ii != mapping.end()) { + std::vector fields = ii->second; + matched = true; + + for (size_t i = 1; i < fields.size(); i++) { + std::string joinkey = header[i]; + std::string joinval = fields[i]; + int attr_type = mvt_string; + + if (joinval.size() > 0) { + if (joinval[0] == '"') { + joinval = csv_dequote(joinval); + } else if (is_number(joinval)) { + attr_type = mvt_double; + } + } else if (pe) { + attr_type = mvt_null; + } - if (ii != mapping.end()) { - std::vector fields = ii->second; - matched = 1; + const char *sjoinkey = joinkey.c_str(); - for (size_t i = 1; i < fields.size(); i++) { - std::string joinkey = header[i]; - std::string joinval = fields[i]; - int attr_type = mvt_string; + if (include.count(joinkey) || (!exclude_all && exclude.count(joinkey) == 0 && exclude_attributes.count(joinkey) == 0 && attr_type != mvt_null)) { + mvt_value outval; + if (attr_type == mvt_string) { + outval.type = mvt_string; + outval.set_string_value(joinval); + } else { + outval.type = mvt_double; + outval.numeric_value.double_value = atof(joinval.c_str()); + } - if (joinval.size() > 0) { - if (joinval[0] == '"') { - joinval = csv_dequote(joinval); - } else if (is_number(joinval)) { - attr_type = mvt_double; - } - } else if (pe) { - attr_type = mvt_null; - } + auto fa = m.attributes.find(sjoinkey); + if (fa != m.attributes.end()) { + m.attributes.erase(fa); + } - const char *sjoinkey = joinkey.c_str(); + serial_val outsv; + outsv.type = outval.type; + outsv.s = joinval; - if (include.count(joinkey) || (!exclude_all && exclude.count(joinkey) == 0 && exclude_attributes.count(joinkey) == 0 && attr_type != mvt_null)) { - mvt_value outval; - if (attr_type == mvt_string) { - outval.type = mvt_string; - outval.set_string_value(joinval); - } else { - outval.type = mvt_double; - outval.numeric_value.double_value = atof(joinval.c_str()); - } + outval = stringified_to_mvt_value(outval.type, joinval.c_str(), tile_stringpool); - auto fa = attributes.find(sjoinkey); - if (fa != attributes.end()) { - attributes.erase(fa); + m.attributes.insert(std::pair>(joinkey, std::pair(outval, outsv))); + m.key_order.push_back(joinkey); } + } + } + } + } - serial_val outsv; - outsv.type = outval.type; - outsv.s = joinval; + if (matched) { + matches.push_back(m); + } + } - outval = stringified_to_mvt_value(outval.type, joinval.c_str(), tile_stringpool); + if (!matched && !ifmatched) { + // no matches, but they said to keep even unmatched tile features, + // so make one that is just the original feature - attributes.insert(std::pair>(joinkey, std::pair(outval, outsv))); - key_order.push_back(joinkey); - } + match m; + m.id = feat.id; + m.has_id = feat.has_id; + + if (!exclude_all_tile_attributes) { + for (size_t t = 0; t + 1 < feat.tags.size(); t += 2) { + const std::string &key = layer.keys[feat.tags[t]]; + mvt_value &val = layer.values[feat.tags[t + 1]]; + serial_val sv = mvt_value_to_serial_val(val); + + if (include.count(key) || (!exclude_all && exclude.count(key) == 0 && exclude_attributes.count(key) == 0)) { + m.attributes.insert(std::pair>(key, std::pair(val, sv))); + m.key_order.push_back(key); } } } + + matches.push_back(m); } - if (matched || !ifmatched) { + for (auto &m : matches) { if (tilestats == layermap.end()) { layermap.insert(std::pair(layer.name, layermap_entry(layermap.size()))); tilestats = layermap.find(layer.name); @@ -402,14 +462,18 @@ void append_tile(std::string message, int z, unsigned x, unsigned y, std::mapsecond.maxzoom = z; } + mvt_feature outfeature; + outfeature.id = m.id; + outfeature.has_id = m.has_id; + // To keep attributes in their original order instead of alphabetical - for (auto k : key_order) { - auto fa = attributes.find(k); + for (auto k : m.key_order) { + auto fa = m.attributes.find(k); - if (fa != attributes.end()) { + if (fa != m.attributes.end()) { outlayer.tag(outfeature, k, fa->second.first); add_to_tilestats(tilestats->second.tilestats, k, fa->second.second); - attributes.erase(fa); + m.attributes.erase(fa); } } From cfd877267e4ea0f20018d99031191d97fddb5981 Mon Sep 17 00:00:00 2001 From: Erica Fischer Date: Tue, 7 Jan 2025 16:45:19 -0800 Subject: [PATCH 27/29] Add test of sql join with limit --- Makefile | 7 +++++ tests/join-sql/countries-limit3.pmtiles.json | 29 +++++++++++++++++++ tests/join-sql/countries.gpkg | Bin 73728 -> 73728 bytes 3 files changed, 36 insertions(+) create mode 100644 tests/join-sql/countries-limit3.pmtiles.json diff --git a/Makefile b/Makefile index 30743475..fc40467b 100644 --- a/Makefile +++ b/Makefile @@ -585,6 +585,13 @@ join-test: tippecanoe tippecanoe-decode tile-join ./tippecanoe-decode -x generator tests/join-sql/countries.pmtiles > tests/join-sql/countries.pmtiles.json.check cmp tests/join-sql/countries.pmtiles.json.check tests/join-sql/countries.pmtiles.json rm -f tests/join-sql/countries.pmtiles tests/join-sql/countries.pmtiles.json.check + # + # Test sql join with limit + # + ./tile-join --join-count-limit 3 -i -f -o tests/join-sql/countries-limit3.pmtiles --join-sqlite tests/join-sql/countries.gpkg --join-table countries --join-tile-attribute ne10-admin0:name_en --join-table-expression 'lower(country)' tests/join-sql/bboxes.pmtiles + ./tippecanoe-decode -x generator tests/join-sql/countries-limit3.pmtiles > tests/join-sql/countries-limit3.pmtiles.json.check + cmp tests/join-sql/countries-limit3.pmtiles.json.check tests/join-sql/countries-limit3.pmtiles.json + rm -f tests/join-sql/countries-limit3.pmtiles tests/join-sql/countries-limit3.pmtiles.json.check accumulate-test: # there are 144 features with POP1950 in the original dataset diff --git a/tests/join-sql/countries-limit3.pmtiles.json b/tests/join-sql/countries-limit3.pmtiles.json new file mode 100644 index 00000000..c67fd0ba --- /dev/null +++ b/tests/join-sql/countries-limit3.pmtiles.json @@ -0,0 +1,29 @@ +{ "type": "FeatureCollection", "properties": { +"antimeridian_adjusted_bounds": "-61.797841,-21.370782,55.854503,55.065334", +"bounds": "-61.797841,-21.370782,55.854503,55.065334", +"center": "0.000000,0.000000,0", +"description": "/tmp/tmp588djzw6/bboxes.pmtiles", +"format": "pbf", +"generator_options": "tippecanoe '--tile-stats-values-limit=1' -o /tmp/tmp588djzw6/bboxes.pmtiles -l parsed-bboxes -U1 '--smallest-maximum-zoom-guess=7' -Bg --drop-fraction-as-needed -P --hilbert --generate-variable-depth-tile-pyramid -rp -b0 '--extend-zooms-if-still-dropping-maximum=3' '--extra-detail=30' -D10 '--tiny-polygon-size=1' --no-tiny-polygon-reduction-at-maximum-zoom '--simplification=1' '--simplification-at-maximum-zoom=0.25' --detect-longitude-wraparound --preserve-input-order '--maximum-tile-bytes=2621440' '--maximum-string-attribute-length=65536' --preserve-point-density-threshold 64 --set-attribute '{\"felt:cluster_size\": 1}' --accumulate-attribute '{\"felt:cluster_size\": \"sum\"}' --preserve-multiplier-density-threshold 512 '--accumulate-numeric-attributes=felt' /tmp/tmpg9jf8evz/parsed.geojsonseq.gz; ./tile-join --join-count-limit 3 -i -f -o tests/join-sql/countries-limit3.pmtiles --join-sqlite tests/join-sql/countries.gpkg --join-table countries --join-tile-attribute ne10-admin0:name_en --join-table-expression 'lower(country)' tests/join-sql/bboxes.pmtiles", +"json": "{\"vector_layers\":[{\"id\":\"parsed-bboxes\",\"description\":\"\",\"minzoom\":0,\"maxzoom\":0,\"fields\":{\"another\":\"String\",\"country\":\"String\",\"felt:cluster_size\":\"Number\",\"fid\":\"Number\",\"ne10-admin0:iso_a2_eh\":\"String\",\"ne10-admin0:iso_a3_eh\":\"String\",\"ne10-admin0:name_en\":\"String\",\"something\":\"String\"}}],\"tilestats\":{\"layerCount\":1,\"layers\":[{\"layer\":\"parsed-bboxes\",\"count\":5,\"geometry\":\"Polygon\",\"attributeCount\":8,\"attributes\":[{\"attribute\":\"another\",\"count\":5,\"type\":\"string\",\"values\":[\"bar\",\"deux\",\"two\",\"why\",\"yes\"]},{\"attribute\":\"country\",\"count\":3,\"type\":\"string\",\"values\":[\"France\",\"Germany\",\"Italy\"]},{\"attribute\":\"felt:cluster_size\",\"count\":1,\"type\":\"number\",\"values\":[1],\"min\":1,\"max\":1},{\"attribute\":\"fid\",\"count\":5,\"type\":\"number\",\"values\":[1,2,3,4,5],\"min\":1,\"max\":5},{\"attribute\":\"ne10-admin0:iso_a2_eh\",\"count\":3,\"type\":\"string\",\"values\":[\"de\",\"fr\",\"it\"]},{\"attribute\":\"ne10-admin0:iso_a3_eh\",\"count\":3,\"type\":\"string\",\"values\":[\"deu\",\"fra\",\"ita\"]},{\"attribute\":\"ne10-admin0:name_en\",\"count\":3,\"type\":\"string\",\"values\":[\"france\",\"germany\",\"italy\"]},{\"attribute\":\"something\",\"count\":5,\"type\":\"string\",\"values\":[\"blah\",\"foo\",\"one\",\"un\",\"what\"]}]}]}}", +"maxzoom": "0", +"minzoom": "0", +"name": "/tmp/tmp588djzw6/bboxes.pmtiles", +"strategies": "[{\"truncated_zooms\":1}]", +"type": "overlay", +"version": "2" +}, "features": [ +{ "type": "FeatureCollection", "properties": { "zoom": 0, "x": 0, "y": 0 }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "parsed-bboxes", "version": 2, "extent": 1073741824 }, "features": [ +{ "type": "Feature", "id": 322, "properties": { "ne10-admin0:name_en": "germany", "ne10-admin0:iso_a2_eh": "de", "ne10-admin0:iso_a3_eh": "deu", "felt:cluster_size": 1, "another": "yes", "country": "Germany", "fid": 1, "something": "blah" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 15.022060, 47.271121 ], [ 5.852490, 47.271121 ], [ 5.852490, 55.065334 ], [ 15.022060, 55.065334 ], [ 15.022060, 47.271121 ] ] ] } } +, +{ "type": "Feature", "id": 435, "properties": { "ne10-admin0:name_en": "france", "ne10-admin0:iso_a2_eh": "fr", "ne10-admin0:iso_a3_eh": "fra", "felt:cluster_size": 1, "another": "why", "country": "France", "fid": 2, "something": "what" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -61.797841, -21.370782 ], [ -61.797841, 51.087541 ], [ 55.854503, 51.087541 ], [ 55.854503, -21.370782 ], [ -61.797841, -21.370782 ] ] ] } } +, +{ "type": "Feature", "id": 435, "properties": { "ne10-admin0:name_en": "france", "ne10-admin0:iso_a2_eh": "fr", "ne10-admin0:iso_a3_eh": "fra", "felt:cluster_size": 1, "another": "two", "country": "France", "fid": 4, "something": "one" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -61.797841, -21.370782 ], [ -61.797841, 51.087541 ], [ 55.854503, 51.087541 ], [ 55.854503, -21.370782 ], [ -61.797841, -21.370782 ] ] ] } } +, +{ "type": "Feature", "id": 435, "properties": { "ne10-admin0:name_en": "france", "ne10-admin0:iso_a2_eh": "fr", "ne10-admin0:iso_a3_eh": "fra", "felt:cluster_size": 1, "another": "deux", "country": "France", "fid": 5, "something": "un" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -61.797841, -21.370782 ], [ -61.797841, 51.087541 ], [ 55.854503, 51.087541 ], [ 55.854503, -21.370782 ], [ -61.797841, -21.370782 ] ] ] } } +, +{ "type": "Feature", "id": 650, "properties": { "ne10-admin0:name_en": "italy", "ne10-admin0:iso_a2_eh": "it", "ne10-admin0:iso_a3_eh": "ita", "felt:cluster_size": 1, "another": "bar", "country": "Italy", "fid": 3, "something": "foo" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 18.517426, 35.489244 ], [ 6.602728, 35.489244 ], [ 6.602728, 47.085215 ], [ 18.517426, 47.085215 ], [ 18.517426, 35.489244 ] ] ] } } +] } +] } +] } diff --git a/tests/join-sql/countries.gpkg b/tests/join-sql/countries.gpkg index c549765d6ef10ebf5b6d260a677b271eae344a98..b7d8fa2c1bf79e274a7810084b7acb8577860ab8 100644 GIT binary patch delta 113 zcmZoTz|wGlWr8##*F+g-RxSp;c-f69v*Q`rHYKnu5MbkzyyH61dtm62ay~@1qT2wc8jrOn_mF~vS0+D3Ih+t01xXA*AK Date: Wed, 8 Jan 2025 08:41:08 -0800 Subject: [PATCH 28/29] Allow multiple tile features to have the same join key --- tile-join.cpp | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/tile-join.cpp b/tile-join.cpp index a0c75faf..d9484a28 100644 --- a/tile-join.cpp +++ b/tile-join.cpp @@ -92,7 +92,7 @@ std::vector>> get_joined_rows(sqlit std::string query = s; sqlite3_free((void *) s); - std::map key_to_row; + std::multimap key_to_row; for (size_t i = 0; i < join_keys.size(); i++) { const mvt_value &v = join_keys[i]; @@ -129,29 +129,31 @@ std::vector>> get_joined_rows(sqlit if (count > 0) { // join key is 0th column of query std::string key = (const char *) sqlite3_column_text(stmt, 0); - auto f = key_to_row.find(key); - if (f == key_to_row.end()) { + auto f = key_to_row.equal_range(key); + if (f.first == f.second) { fprintf(stderr, "Unexpected join key: %s\n", key.c_str()); continue; } - if (ret[f->second].size() < join_count_limit) { - for (int i = 1; i < count; i++) { - int type = sqlite3_column_type(stmt, i); - mvt_value v; - v.type = mvt_null; + for (auto ff = f.first; ff != f.second; ++ff) { + if (ret[ff->second].size() < join_count_limit) { + for (int i = 1; i < count; i++) { + int type = sqlite3_column_type(stmt, i); + mvt_value v; + v.type = mvt_null; + + if (type == SQLITE_INTEGER || type == SQLITE_FLOAT) { + v = mvt_value(sqlite3_column_double(stmt, i)); + } else if (type == SQLITE_TEXT || type == SQLITE_BLOB) { + v.set_string_value((const char *) sqlite3_column_text(stmt, i)); + } - if (type == SQLITE_INTEGER || type == SQLITE_FLOAT) { - v = mvt_value(sqlite3_column_double(stmt, i)); - } else if (type == SQLITE_TEXT || type == SQLITE_BLOB) { - v.set_string_value((const char *) sqlite3_column_text(stmt, i)); + const char *name = sqlite3_column_name(stmt, i); + row.emplace(name, v); } - const char *name = sqlite3_column_name(stmt, i); - row.emplace(name, v); + ret[ff->second].push_back(row); } - - ret[f->second].push_back(std::move(row)); } } } From e6c79e5b4ae7d7be8763347ea9faa34a1d09e2ea Mon Sep 17 00:00:00 2001 From: Erica Fischer Date: Wed, 8 Jan 2025 08:47:05 -0800 Subject: [PATCH 29/29] Update tests for a country name with two distinct geometries --- tests/join-sql/countries-limit3.pmtiles.json | 10 +++++++--- tests/join-sql/countries.gpkg | Bin 73728 -> 73728 bytes tests/join-sql/countries.pmtiles.json | 10 +++++++--- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/tests/join-sql/countries-limit3.pmtiles.json b/tests/join-sql/countries-limit3.pmtiles.json index c67fd0ba..86f396fc 100644 --- a/tests/join-sql/countries-limit3.pmtiles.json +++ b/tests/join-sql/countries-limit3.pmtiles.json @@ -1,11 +1,11 @@ { "type": "FeatureCollection", "properties": { -"antimeridian_adjusted_bounds": "-61.797841,-21.370782,55.854503,55.065334", -"bounds": "-61.797841,-21.370782,55.854503,55.065334", +"antimeridian_adjusted_bounds": "-63.146840,-21.370782,55.854503,55.065334", +"bounds": "-63.146840,-21.370782,55.854503,55.065334", "center": "0.000000,0.000000,0", "description": "/tmp/tmp588djzw6/bboxes.pmtiles", "format": "pbf", "generator_options": "tippecanoe '--tile-stats-values-limit=1' -o /tmp/tmp588djzw6/bboxes.pmtiles -l parsed-bboxes -U1 '--smallest-maximum-zoom-guess=7' -Bg --drop-fraction-as-needed -P --hilbert --generate-variable-depth-tile-pyramid -rp -b0 '--extend-zooms-if-still-dropping-maximum=3' '--extra-detail=30' -D10 '--tiny-polygon-size=1' --no-tiny-polygon-reduction-at-maximum-zoom '--simplification=1' '--simplification-at-maximum-zoom=0.25' --detect-longitude-wraparound --preserve-input-order '--maximum-tile-bytes=2621440' '--maximum-string-attribute-length=65536' --preserve-point-density-threshold 64 --set-attribute '{\"felt:cluster_size\": 1}' --accumulate-attribute '{\"felt:cluster_size\": \"sum\"}' --preserve-multiplier-density-threshold 512 '--accumulate-numeric-attributes=felt' /tmp/tmpg9jf8evz/parsed.geojsonseq.gz; ./tile-join --join-count-limit 3 -i -f -o tests/join-sql/countries-limit3.pmtiles --join-sqlite tests/join-sql/countries.gpkg --join-table countries --join-tile-attribute ne10-admin0:name_en --join-table-expression 'lower(country)' tests/join-sql/bboxes.pmtiles", -"json": "{\"vector_layers\":[{\"id\":\"parsed-bboxes\",\"description\":\"\",\"minzoom\":0,\"maxzoom\":0,\"fields\":{\"another\":\"String\",\"country\":\"String\",\"felt:cluster_size\":\"Number\",\"fid\":\"Number\",\"ne10-admin0:iso_a2_eh\":\"String\",\"ne10-admin0:iso_a3_eh\":\"String\",\"ne10-admin0:name_en\":\"String\",\"something\":\"String\"}}],\"tilestats\":{\"layerCount\":1,\"layers\":[{\"layer\":\"parsed-bboxes\",\"count\":5,\"geometry\":\"Polygon\",\"attributeCount\":8,\"attributes\":[{\"attribute\":\"another\",\"count\":5,\"type\":\"string\",\"values\":[\"bar\",\"deux\",\"two\",\"why\",\"yes\"]},{\"attribute\":\"country\",\"count\":3,\"type\":\"string\",\"values\":[\"France\",\"Germany\",\"Italy\"]},{\"attribute\":\"felt:cluster_size\",\"count\":1,\"type\":\"number\",\"values\":[1],\"min\":1,\"max\":1},{\"attribute\":\"fid\",\"count\":5,\"type\":\"number\",\"values\":[1,2,3,4,5],\"min\":1,\"max\":5},{\"attribute\":\"ne10-admin0:iso_a2_eh\",\"count\":3,\"type\":\"string\",\"values\":[\"de\",\"fr\",\"it\"]},{\"attribute\":\"ne10-admin0:iso_a3_eh\",\"count\":3,\"type\":\"string\",\"values\":[\"deu\",\"fra\",\"ita\"]},{\"attribute\":\"ne10-admin0:name_en\",\"count\":3,\"type\":\"string\",\"values\":[\"france\",\"germany\",\"italy\"]},{\"attribute\":\"something\",\"count\":5,\"type\":\"string\",\"values\":[\"blah\",\"foo\",\"one\",\"un\",\"what\"]}]}]}}", +"json": "{\"vector_layers\":[{\"id\":\"parsed-bboxes\",\"description\":\"\",\"minzoom\":0,\"maxzoom\":0,\"fields\":{\"another\":\"String\",\"country\":\"String\",\"felt:cluster_size\":\"Number\",\"fid\":\"Number\",\"ne10-admin0:iso_a2_eh\":\"String\",\"ne10-admin0:iso_a3_eh\":\"String\",\"ne10-admin0:name_en\":\"String\",\"something\":\"String\"}}],\"tilestats\":{\"layerCount\":1,\"layers\":[{\"layer\":\"parsed-bboxes\",\"count\":7,\"geometry\":\"Polygon\",\"attributeCount\":8,\"attributes\":[{\"attribute\":\"another\",\"count\":6,\"type\":\"string\",\"values\":[\"bar\",\"deux\",\"hey\",\"two\",\"why\",\"yes\"]},{\"attribute\":\"country\",\"count\":4,\"type\":\"string\",\"values\":[\"France\",\"Germany\",\"Italy\",\"saint-martin\"]},{\"attribute\":\"felt:cluster_size\",\"count\":1,\"type\":\"number\",\"values\":[1],\"min\":1,\"max\":1},{\"attribute\":\"fid\",\"count\":6,\"type\":\"number\",\"values\":[1,2,3,4,5,7],\"min\":1,\"max\":7},{\"attribute\":\"ne10-admin0:iso_a2_eh\",\"count\":5,\"type\":\"string\",\"values\":[\"de\",\"fr\",\"it\",\"mf\",\"sx\"]},{\"attribute\":\"ne10-admin0:iso_a3_eh\",\"count\":5,\"type\":\"string\",\"values\":[\"deu\",\"fra\",\"ita\",\"maf\",\"sxm\"]},{\"attribute\":\"ne10-admin0:name_en\",\"count\":4,\"type\":\"string\",\"values\":[\"france\",\"germany\",\"italy\",\"saint-martin\"]},{\"attribute\":\"something\",\"count\":6,\"type\":\"string\",\"values\":[\"blah\",\"foo\",\"hey\",\"one\",\"un\",\"what\"]}]}]}}", "maxzoom": "0", "minzoom": "0", "name": "/tmp/tmp588djzw6/bboxes.pmtiles", @@ -24,6 +24,10 @@ { "type": "Feature", "id": 435, "properties": { "ne10-admin0:name_en": "france", "ne10-admin0:iso_a2_eh": "fr", "ne10-admin0:iso_a3_eh": "fra", "felt:cluster_size": 1, "another": "deux", "country": "France", "fid": 5, "something": "un" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -61.797841, -21.370782 ], [ -61.797841, 51.087541 ], [ 55.854503, 51.087541 ], [ 55.854503, -21.370782 ], [ -61.797841, -21.370782 ] ] ] } } , { "type": "Feature", "id": 650, "properties": { "ne10-admin0:name_en": "italy", "ne10-admin0:iso_a2_eh": "it", "ne10-admin0:iso_a3_eh": "ita", "felt:cluster_size": 1, "another": "bar", "country": "Italy", "fid": 3, "something": "foo" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 18.517426, 35.489244 ], [ 6.602728, 35.489244 ], [ 6.602728, 47.085215 ], [ 18.517426, 47.085215 ], [ 18.517426, 35.489244 ] ] ] } } +, +{ "type": "Feature", "id": 1546, "properties": { "ne10-admin0:name_en": "saint-martin", "ne10-admin0:iso_a2_eh": "mf", "ne10-admin0:iso_a3_eh": "maf", "felt:cluster_size": 1, "another": "hey", "country": "saint-martin", "fid": 7, "something": "hey" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -63.010731, 18.033391 ], [ -63.146840, 18.033391 ], [ -63.146840, 18.122138 ], [ -63.010731, 18.122138 ], [ -63.010731, 18.033391 ] ] ] } } +, +{ "type": "Feature", "id": 1611, "properties": { "ne10-admin0:name_en": "saint-martin", "ne10-admin0:iso_a2_eh": "sx", "ne10-admin0:iso_a3_eh": "sxm", "felt:cluster_size": 1, "another": "hey", "country": "saint-martin", "fid": 7, "something": "hey" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -63.017568, 18.019110 ], [ -63.118886, 18.019110 ], [ -63.118886, 18.062120 ], [ -63.017568, 18.062120 ], [ -63.017568, 18.019110 ] ] ] } } ] } ] } ] } diff --git a/tests/join-sql/countries.gpkg b/tests/join-sql/countries.gpkg index b7d8fa2c1bf79e274a7810084b7acb8577860ab8..80a36ceb6cbab3629fd8320b37d52afd299e6cde 100644 GIT binary patch delta 83 zcmZoTz|wGlWr8##_e2?IM(&LX^Wz!WHzlwv5Mt*qXW)O$f1Up*|0e$V{1f=gHw!8x m@=J=dvofd(3l}G5=9TE?CKi=s=4GT-0@3yf{EYbmjO+j=V;Kwp delta 55 zcmV-70LcG