From 178b5d0054471747835d44ff5464e67267d69605 Mon Sep 17 00:00:00 2001 From: Eric Fischer Date: Mon, 17 Apr 2017 14:42:50 -0700 Subject: [PATCH] Add -T option to coerce the types of feature attributes --- CHANGELOG.md | 4 + README.md | 5 + geojson.cpp | 109 +++++++++++++----- geojson.hpp | 3 +- main.cpp | 56 +++++++-- man/tippecanoe.1 | 6 + tests/attribute-type/in.json | 39 +++++++ ...t_-Tbooltype@bool_-Tstringtype@string.json | 94 +++++++++++++++ version.hpp | 2 +- 9 files changed, 275 insertions(+), 43 deletions(-) create mode 100644 tests/attribute-type/in.json create mode 100644 tests/attribute-type/out/-z0_-Tinttype@int_-Tfloattype@float_-Tbooltype@bool_-Tstringtype@string.json diff --git a/CHANGELOG.md b/CHANGELOG.md index b474fb2..334c3f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.17.1 + +* Add -T option to coerce the types of feature attributes + ## 1.17.0 * Add -zg option to guess an appropriate maxzoom diff --git a/README.md b/README.md index 494d6b6..13fa7cb 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,11 @@ resolution is obtained than by using a smaller _maxzoom_ or _detail_. * `-x` _name_ or `--exclude=`_name_: Exclude the named properties from all features * `-y` _name_ or `--include=`_name_: Include the named properties in all features, excluding all those not explicitly named * `-X` or `--exclude-all`: Exclude all properties and encode only geometries + * `-T`_attribute_`:`_type_ or `--attribute-type=`_attribute_`:`_type_: Coerce the named feature _attribute_ to be of the specified _type_. + The _type_ may be `string`, `float`, `int`, or `bool`. + If the type is `bool`, then original attributes of `0`, `false`, `null`, or the empty string become `false`, and otherwise become `true`. + If the type is `float` or `int` and the original attribute was non-numeric, it becomes `0`. + If the type is `int` and the original attribute was floating-point, it is rounded to the nearest integer. ### Dropping a fixed fraction of features by zoom level diff --git a/geojson.cpp b/geojson.cpp index ffbcebe..cb40ae4 100644 --- a/geojson.cpp +++ b/geojson.cpp @@ -184,7 +184,7 @@ long long parse_geometry(int t, json_object *j, long long *bbox, drawvec &out, i return g; } -int serialize_geometry(json_object *geometry, json_object *properties, json_object *id, const char *reading, int line, volatile long long *layer_seq, volatile long long *progress_seq, long long *metapos, long long *geompos, long long *indexpos, std::set *exclude, std::set *include, int exclude_all, FILE *metafile, FILE *geomfile, FILE *indexfile, struct memfile *poolfile, struct memfile *treefile, const char *fname, int basezoom, int layer, double droprate, long long *file_bbox, json_object *tippecanoe, int segment, int *initialized, unsigned *initial_x, unsigned *initial_y, struct reader *readers, int maxzoom, json_object *feature, std::map *layermap, std::string layername, bool uses_gamma) { +int serialize_geometry(json_object *geometry, json_object *properties, json_object *id, const char *reading, int line, volatile long long *layer_seq, volatile long long *progress_seq, long long *metapos, long long *geompos, long long *indexpos, std::set *exclude, std::set *include, int exclude_all, FILE *metafile, FILE *geomfile, FILE *indexfile, struct memfile *poolfile, struct memfile *treefile, const char *fname, int basezoom, int layer, double droprate, long long *file_bbox, json_object *tippecanoe, int segment, int *initialized, unsigned *initial_x, unsigned *initial_y, struct reader *readers, int maxzoom, json_object *feature, std::map *layermap, std::string layername, bool uses_gamma, std::map const *attribute_types) { json_object *geometry_type = json_hash_get(geometry, "type"); if (geometry_type == NULL) { static int warned = 0; @@ -333,32 +333,79 @@ int serialize_geometry(json_object *geometry, json_object *properties, json_obje metakey[m] = properties->keys[i]->string; - if (properties->values[i] != NULL && properties->values[i]->type == JSON_STRING) { - tas.type = metatype[m] = mvt_string; - metaval[m] = std::string(properties->values[i]->string); - std::string err = check_utf8(metaval[m]); - if (err != "") { - fprintf(stderr, "%s:%d: %s\n", reading, line, err.c_str()); - json_context(feature); - exit(EXIT_FAILURE); + if (properties->values[i] != NULL) { + int vt = properties->values[i]->type; + std::string val; + + if (vt == JSON_STRING || vt == JSON_NUMBER) { + val = properties->values[i]->string; + } else if (vt == JSON_TRUE) { + val = "true"; + } else if (vt == JSON_FALSE) { + val = "false"; + } else if (vt == JSON_NULL) { + val = "null"; + } else { + const char *v = json_stringify(properties->values[i]); + val = std::string(v); + free((void *) v); // stringify + } + + auto a = (*attribute_types).find(properties->keys[i]->string); + if (a != attribute_types->end()) { + if (a->second == mvt_string) { + vt = JSON_STRING; + } else if (a->second == mvt_float) { + vt = JSON_NUMBER; + } else if (a->second == mvt_int) { + vt = JSON_NUMBER; + + for (size_t ii = 0; ii < val.size(); ii++) { + char c = val[ii]; + if (c < '0' || c > '9') { + val = std::to_string(round(atof(val.c_str()))); + break; + } + } + } else if (a->second == mvt_bool) { + if (val == "false" || val == "0" || val == "null" || val.size() == 0) { + vt = JSON_FALSE; + val = "false"; + } else { + vt = JSON_TRUE; + val = "true"; + } + } else { + fprintf(stderr, "Can't happen: attribute type %d\n", a->second); + exit(EXIT_FAILURE); + } + } + + if (vt == JSON_STRING) { + tas.type = metatype[m] = mvt_string; + metaval[m] = val; + std::string err = check_utf8(metaval[m]); + if (err != "") { + fprintf(stderr, "%s:%d: %s\n", reading, line, err.c_str()); + json_context(feature); + exit(EXIT_FAILURE); + } + m++; + } else if (vt == JSON_NUMBER) { + tas.type = metatype[m] = mvt_double; + metaval[m] = val; + m++; + } else if (vt == JSON_TRUE || vt == JSON_FALSE) { + tas.type = metatype[m] = mvt_bool; + metaval[m] = val; + m++; + } else if (vt == JSON_NULL) { + ; + } else { + tas.type = metatype[m] = mvt_string; + metaval[m] = val; + m++; } - m++; - } else if (properties->values[i] != NULL && properties->values[i]->type == JSON_NUMBER) { - tas.type = metatype[m] = mvt_double; - metaval[m] = std::string(properties->values[i]->string); - m++; - } else if (properties->values[i] != NULL && (properties->values[i]->type == JSON_TRUE || properties->values[i]->type == JSON_FALSE)) { - tas.type = metatype[m] = mvt_bool; - metaval[m] = std::string(properties->values[i]->type == JSON_TRUE ? "true" : "false"); - m++; - } else if (properties->values[i] != NULL && (properties->values[i]->type == JSON_NULL)) { - ; - } else { - tas.type = metatype[m] = mvt_string; - const char *v = json_stringify(properties->values[i]); - metaval[m] = std::string(v); - free((void *) v); // stringify - m++; } if (tas.type >= 0) { @@ -527,7 +574,7 @@ void check_crs(json_object *j, const char *reading) { } } -void parse_json(json_pull *jp, const char *reading, volatile long long *layer_seq, volatile long long *progress_seq, long long *metapos, long long *geompos, long long *indexpos, std::set *exclude, std::set *include, int exclude_all, FILE *metafile, FILE *geomfile, FILE *indexfile, struct memfile *poolfile, struct memfile *treefile, char *fname, int basezoom, int layer, double droprate, long long *file_bbox, int segment, int *initialized, unsigned *initial_x, unsigned *initial_y, struct reader *readers, int maxzoom, std::map *layermap, std::string layername, bool uses_gamma) { +void parse_json(json_pull *jp, const char *reading, volatile long long *layer_seq, volatile long long *progress_seq, long long *metapos, long long *geompos, long long *indexpos, std::set *exclude, std::set *include, int exclude_all, FILE *metafile, FILE *geomfile, FILE *indexfile, struct memfile *poolfile, struct memfile *treefile, char *fname, int basezoom, int layer, double droprate, long long *file_bbox, int segment, int *initialized, unsigned *initial_x, unsigned *initial_y, struct reader *readers, int maxzoom, std::map *layermap, std::string layername, bool uses_gamma, std::map const *attribute_types) { long long found_hashes = 0; long long found_features = 0; long long found_geometries = 0; @@ -595,7 +642,7 @@ void parse_json(json_pull *jp, const char *reading, volatile long long *layer_se } found_geometries++; - serialize_geometry(j, NULL, NULL, reading, jp->line, layer_seq, progress_seq, metapos, geompos, indexpos, exclude, include, exclude_all, metafile, geomfile, indexfile, poolfile, treefile, fname, basezoom, layer, droprate, file_bbox, NULL, segment, initialized, initial_x, initial_y, readers, maxzoom, j, layermap, layername, uses_gamma); + serialize_geometry(j, NULL, NULL, reading, jp->line, layer_seq, progress_seq, metapos, geompos, indexpos, exclude, include, exclude_all, metafile, geomfile, indexfile, poolfile, treefile, fname, basezoom, layer, droprate, file_bbox, NULL, segment, initialized, initial_x, initial_y, readers, maxzoom, j, layermap, layername, uses_gamma, attribute_types); json_free(j); continue; } @@ -638,10 +685,10 @@ void parse_json(json_pull *jp, const char *reading, volatile long long *layer_se if (geometries != NULL) { size_t g; for (g = 0; g < geometries->length; g++) { - serialize_geometry(geometries->array[g], properties, id, reading, jp->line, layer_seq, progress_seq, metapos, geompos, indexpos, exclude, include, exclude_all, metafile, geomfile, indexfile, poolfile, treefile, fname, basezoom, layer, droprate, file_bbox, tippecanoe, segment, initialized, initial_x, initial_y, readers, maxzoom, j, layermap, layername, uses_gamma); + serialize_geometry(geometries->array[g], properties, id, reading, jp->line, layer_seq, progress_seq, metapos, geompos, indexpos, exclude, include, exclude_all, metafile, geomfile, indexfile, poolfile, treefile, fname, basezoom, layer, droprate, file_bbox, tippecanoe, segment, initialized, initial_x, initial_y, readers, maxzoom, j, layermap, layername, uses_gamma, attribute_types); } } else { - serialize_geometry(geometry, properties, id, reading, jp->line, layer_seq, progress_seq, metapos, geompos, indexpos, exclude, include, exclude_all, metafile, geomfile, indexfile, poolfile, treefile, fname, basezoom, layer, droprate, file_bbox, tippecanoe, segment, initialized, initial_x, initial_y, readers, maxzoom, j, layermap, layername, uses_gamma); + serialize_geometry(geometry, properties, id, reading, jp->line, layer_seq, progress_seq, metapos, geompos, indexpos, exclude, include, exclude_all, metafile, geomfile, indexfile, poolfile, treefile, fname, basezoom, layer, droprate, file_bbox, tippecanoe, segment, initialized, initial_x, initial_y, readers, maxzoom, j, layermap, layername, uses_gamma, attribute_types); } json_free(j); @@ -653,7 +700,7 @@ void parse_json(json_pull *jp, const char *reading, volatile long long *layer_se void *run_parse_json(void *v) { struct parse_json_args *pja = (struct parse_json_args *) v; - parse_json(pja->jp, pja->reading, pja->layer_seq, pja->progress_seq, pja->metapos, pja->geompos, pja->indexpos, pja->exclude, pja->include, pja->exclude_all, pja->metafile, pja->geomfile, pja->indexfile, pja->poolfile, pja->treefile, pja->fname, pja->basezoom, pja->layer, pja->droprate, pja->file_bbox, pja->segment, pja->initialized, pja->initial_x, pja->initial_y, pja->readers, pja->maxzoom, pja->layermap, *pja->layername, pja->uses_gamma); + parse_json(pja->jp, pja->reading, pja->layer_seq, pja->progress_seq, pja->metapos, pja->geompos, pja->indexpos, pja->exclude, pja->include, pja->exclude_all, pja->metafile, pja->geomfile, pja->indexfile, pja->poolfile, pja->treefile, pja->fname, pja->basezoom, pja->layer, pja->droprate, pja->file_bbox, pja->segment, pja->initialized, pja->initial_x, pja->initial_y, pja->readers, pja->maxzoom, pja->layermap, *pja->layername, pja->uses_gamma, pja->attribute_types); return NULL; } diff --git a/geojson.hpp b/geojson.hpp index db6116f..c040068 100644 --- a/geojson.hpp +++ b/geojson.hpp @@ -28,10 +28,11 @@ struct parse_json_args { std::map *layermap; std::string *layername; bool uses_gamma; + std::map const *attribute_types; }; struct json_pull *json_begin_map(char *map, long long len); void json_end_map(struct json_pull *jp); -void parse_json(json_pull *jp, const char *reading, volatile long long *layer_seq, volatile long long *progress_seq, long long *metapos, long long *geompos, long long *indexpos, std::set *exclude, std::set *include, int exclude_all, FILE *metafile, FILE *geomfile, FILE *indexfile, struct memfile *poolfile, struct memfile *treefile, char *fname, int basezoom, int layer, double droprate, long long *file_bbox, int segment, int *initialized, unsigned *initial_x, unsigned *initial_y, struct reader *readers, int maxzoom, std::map *layermap, std::string layername, bool uses_gamma); +void parse_json(json_pull *jp, const char *reading, volatile long long *layer_seq, volatile long long *progress_seq, long long *metapos, long long *geompos, long long *indexpos, std::set *exclude, std::set *include, int exclude_all, FILE *metafile, FILE *geomfile, FILE *indexfile, struct memfile *poolfile, struct memfile *treefile, char *fname, int basezoom, int layer, double droprate, long long *file_bbox, int segment, int *initialized, unsigned *initial_x, unsigned *initial_y, struct reader *readers, int maxzoom, std::map *layermap, std::string layername, bool uses_gamma, std::map const *attribute_types); void *run_parse_json(void *v); diff --git a/main.cpp b/main.cpp index 8d42103..42e7b67 100644 --- a/main.cpp +++ b/main.cpp @@ -372,7 +372,7 @@ void *run_sort(void *v) { return NULL; } -void do_read_parallel(char *map, long long len, long long initial_offset, const char *reading, struct reader *reader, volatile long long *progress_seq, std::set *exclude, std::set *include, int exclude_all, char *fname, int basezoom, int source, int nlayers, std::vector > *layermaps, double droprate, int *initialized, unsigned *initial_x, unsigned *initial_y, int maxzoom, std::string layername, bool uses_gamma) { +void do_read_parallel(char *map, long long len, long long initial_offset, const char *reading, struct reader *reader, volatile long long *progress_seq, std::set *exclude, std::set *include, int exclude_all, char *fname, int basezoom, int source, int nlayers, std::vector > *layermaps, double droprate, int *initialized, unsigned *initial_x, unsigned *initial_y, int maxzoom, std::string layername, bool uses_gamma, std::map const *attribute_types) { long long segs[CPUS + 1]; segs[0] = 0; segs[CPUS] = len; @@ -430,6 +430,7 @@ void do_read_parallel(char *map, long long len, long long initial_offset, const pja[i].layermap = &(*layermaps)[i]; pja[i].layername = &layername; pja[i].uses_gamma = uses_gamma; + pja[i].attribute_types = attribute_types; if (pthread_create(&pthreads[i], NULL, run_parse_json, &pja[i]) != 0) { perror("pthread_create"); @@ -473,6 +474,7 @@ struct read_parallel_arg { unsigned *initial_y; std::string layername; bool uses_gamma; + std::map const *attribute_types; }; void *run_read_parallel(void *v) { @@ -494,7 +496,7 @@ void *run_read_parallel(void *v) { } madvise(map, rpa->len, MADV_RANDOM); // sequential, but from several pointers at once - do_read_parallel(map, rpa->len, rpa->offset, rpa->reading, rpa->reader, rpa->progress_seq, rpa->exclude, rpa->include, rpa->exclude_all, rpa->fname, rpa->basezoom, rpa->source, rpa->nlayers, rpa->layermaps, rpa->droprate, rpa->initialized, rpa->initial_x, rpa->initial_y, rpa->maxzoom, rpa->layername, rpa->uses_gamma); + do_read_parallel(map, rpa->len, rpa->offset, rpa->reading, rpa->reader, rpa->progress_seq, rpa->exclude, rpa->include, rpa->exclude_all, rpa->fname, rpa->basezoom, rpa->source, rpa->nlayers, rpa->layermaps, rpa->droprate, rpa->initialized, rpa->initial_x, rpa->initial_y, rpa->maxzoom, rpa->layername, rpa->uses_gamma, rpa->attribute_types); madvise(map, rpa->len, MADV_DONTNEED); if (munmap(map, rpa->len) != 0) { @@ -511,7 +513,7 @@ void *run_read_parallel(void *v) { return NULL; } -void start_parsing(int fd, FILE *fp, long long offset, long long len, volatile int *is_parsing, pthread_t *parallel_parser, bool &parser_created, const char *reading, struct reader *reader, volatile long long *progress_seq, std::set *exclude, std::set *include, int exclude_all, char *fname, int basezoom, int source, int nlayers, std::vector > &layermaps, double droprate, int *initialized, unsigned *initial_x, unsigned *initial_y, int maxzoom, std::string layername, bool uses_gamma) { +void start_parsing(int fd, FILE *fp, long long offset, long long len, volatile int *is_parsing, pthread_t *parallel_parser, bool &parser_created, const char *reading, struct reader *reader, volatile long long *progress_seq, std::set *exclude, std::set *include, int exclude_all, char *fname, int basezoom, int source, int nlayers, std::vector > &layermaps, double droprate, int *initialized, unsigned *initial_x, unsigned *initial_y, int maxzoom, std::string layername, bool uses_gamma, std::map const *attribute_types) { // This has to kick off an intermediate thread to start the parser threads, // so the main thread can get back to reading the next input stage while // the intermediate thread waits for the completion of the parser threads. @@ -548,6 +550,7 @@ void start_parsing(int fd, FILE *fp, long long offset, long long len, volatile i rpa->maxzoom = maxzoom; rpa->layername = layername; rpa->uses_gamma = uses_gamma; + rpa->attribute_types = attribute_types; if (pthread_create(parallel_parser, NULL, run_read_parallel, rpa) != 0) { perror("pthread_create"); @@ -1006,7 +1009,7 @@ void choose_first_zoom(long long *file_bbox, struct reader *reader, unsigned *iz } } -int read_input(std::vector &sources, char *fname, int &maxzoom, int minzoom, int basezoom, double basezoom_marker_width, sqlite3 *outdb, const char *outdir, std::set *exclude, std::set *include, int exclude_all, double droprate, int buffer, const char *tmpdir, double gamma, int read_parallel, int forcetable, const char *attribution, bool uses_gamma, long long *file_bbox, const char *description, bool guess_maxzoom) { +int read_input(std::vector &sources, char *fname, int &maxzoom, int minzoom, int basezoom, double basezoom_marker_width, sqlite3 *outdb, const char *outdir, std::set *exclude, std::set *include, int exclude_all, double droprate, int buffer, const char *tmpdir, double gamma, int read_parallel, int forcetable, const char *attribution, bool uses_gamma, long long *file_bbox, const char *description, bool guess_maxzoom, std::map const *attribute_types) { int ret = EXIT_SUCCESS; struct reader reader[CPUS]; @@ -1211,7 +1214,7 @@ int read_input(std::vector &sources, char *fname, int &maxzoom, int minz } if (map != NULL && map != MAP_FAILED) { - do_read_parallel(map, st.st_size - off, overall_offset, reading.c_str(), reader, &progress_seq, exclude, include, exclude_all, fname, basezoom, layer, nlayers, &layermaps, droprate, initialized, initial_x, initial_y, maxzoom, sources[layer].layer, uses_gamma); + do_read_parallel(map, st.st_size - off, overall_offset, reading.c_str(), reader, &progress_seq, exclude, include, exclude_all, fname, basezoom, layer, nlayers, &layermaps, droprate, initialized, initial_x, initial_y, maxzoom, sources[layer].layer, uses_gamma, attribute_types); overall_offset += st.st_size - off; checkdisk(reader, CPUS); @@ -1279,7 +1282,7 @@ int read_input(std::vector &sources, char *fname, int &maxzoom, int minz } fflush(readfp); - start_parsing(readfd, readfp, initial_offset, ahead, &is_parsing, ¶llel_parser, parser_created, reading.c_str(), reader, &progress_seq, exclude, include, exclude_all, fname, basezoom, layer, nlayers, layermaps, droprate, initialized, initial_x, initial_y, maxzoom, sources[layer].layer, gamma != 0); + start_parsing(readfd, readfp, initial_offset, ahead, &is_parsing, ¶llel_parser, parser_created, reading.c_str(), reader, &progress_seq, exclude, include, exclude_all, fname, basezoom, layer, nlayers, layermaps, droprate, initialized, initial_x, initial_y, maxzoom, sources[layer].layer, gamma != 0, attribute_types); initial_offset += ahead; overall_offset += ahead; @@ -1316,7 +1319,7 @@ int read_input(std::vector &sources, char *fname, int &maxzoom, int minz fflush(readfp); if (ahead > 0) { - start_parsing(readfd, readfp, initial_offset, ahead, &is_parsing, ¶llel_parser, parser_created, reading.c_str(), reader, &progress_seq, exclude, include, exclude_all, fname, basezoom, layer, nlayers, layermaps, droprate, initialized, initial_x, initial_y, maxzoom, sources[layer].layer, gamma != 0); + start_parsing(readfd, readfp, initial_offset, ahead, &is_parsing, ¶llel_parser, parser_created, reading.c_str(), reader, &progress_seq, exclude, include, exclude_all, fname, basezoom, layer, nlayers, layermaps, droprate, initialized, initial_x, initial_y, maxzoom, sources[layer].layer, gamma != 0, attribute_types); if (parser_created) { if (pthread_join(parallel_parser, NULL) != 0) { @@ -1333,7 +1336,7 @@ int read_input(std::vector &sources, char *fname, int &maxzoom, int minz long long layer_seq = overall_offset; json_pull *jp = json_begin_file(fp); - parse_json(jp, reading.c_str(), &layer_seq, &progress_seq, &reader[0].metapos, &reader[0].geompos, &reader[0].indexpos, exclude, include, exclude_all, reader[0].metafile, reader[0].geomfile, reader[0].indexfile, reader[0].poolfile, reader[0].treefile, fname, basezoom, layer, droprate, reader[0].file_bbox, 0, &initialized[0], &initial_x[0], &initial_y[0], reader, maxzoom, &layermaps[0], sources[layer].layer, uses_gamma); + parse_json(jp, reading.c_str(), &layer_seq, &progress_seq, &reader[0].metapos, &reader[0].geompos, &reader[0].indexpos, exclude, include, exclude_all, reader[0].metafile, reader[0].geomfile, reader[0].indexfile, reader[0].poolfile, reader[0].treefile, fname, basezoom, layer, droprate, reader[0].file_bbox, 0, &initialized[0], &initial_x[0], &initial_y[0], reader, maxzoom, &layermaps[0], sources[layer].layer, uses_gamma, attribute_types); json_end(jp); overall_offset = layer_seq; checkdisk(reader, CPUS); @@ -1924,6 +1927,33 @@ static bool has_name(struct option *long_options, int *pl) { return false; } +void set_attribute_type(std::map &attribute_types, const char *arg) { + const char *s = strchr(arg, ':'); + if (s == NULL) { + fprintf(stderr, "-T%s option must be in the form -Tname:type\n", arg); + exit(EXIT_FAILURE); + } + + std::string name = std::string(arg, s - arg); + std::string type = std::string(s + 1); + int t = -1; + + if (type == "int") { + t = mvt_int; + } else if (type == "float") { + t = mvt_float; + } else if (type == "string") { + t = mvt_string; + } else if (type == "bool") { + t = mvt_bool; + } else { + fprintf(stderr, "Attribute type (%s) must be int, float, string, or bool\n", type.c_str()); + exit(EXIT_FAILURE); + } + + attribute_types.insert(std::pair(name, t)); +} + int main(int argc, char **argv) { #ifdef MTRACE mtrace(); @@ -1956,6 +1986,7 @@ int main(int argc, char **argv) { bool guess_maxzoom = false; std::set exclude, include; + std::map attribute_types; int exclude_all = 0; int read_parallel = 0; int files_open_at_start; @@ -1991,6 +2022,7 @@ int main(int argc, char **argv) { {"projection", required_argument, 0, 's'}, {"simplification", required_argument, 0, 'S'}, {"maximum-tile-bytes", required_argument, 0, 'M'}, + {"attribute-type", required_argument, 0, 'T'}, {"exclude-all", no_argument, 0, 'X'}, {"force", no_argument, 0, 'f'}, @@ -2048,7 +2080,7 @@ int main(int argc, char **argv) { } } - while ((i = getopt_long(argc, argv, "n:l:z:Z:B:d:D:m:o:e:x:y:r:b:t:g:p:a:XfFqvPL:A:s:S:M:N:", long_options, NULL)) != -1) { + while ((i = getopt_long(argc, argv, "n:l:z:Z:B:d:D:m:o:e:x:y:r:b:t:g:p:a:XfFqvPL:A:s:S:M:N:T:", long_options, NULL)) != -1) { switch (i) { case 0: break; @@ -2244,6 +2276,10 @@ int main(int argc, char **argv) { max_tile_size = atoll(optarg); break; + case 'T': + set_attribute_type(attribute_types, optarg); + break; + default: { int width = 7 + strlen(argv[0]); fprintf(stderr, "Unknown option -%c\n", i); @@ -2366,7 +2402,7 @@ int main(int argc, char **argv) { long long file_bbox[4] = {UINT_MAX, UINT_MAX, 0, 0}; - ret = read_input(sources, name ? name : out_mbtiles ? out_mbtiles : out_directory, maxzoom, minzoom, basezoom, basezoom_marker_width, outdb, out_directory, &exclude, &include, exclude_all, droprate, buffer, tmpdir, gamma, read_parallel, forcetable, attribution, gamma != 0, file_bbox, description, guess_maxzoom); + ret = read_input(sources, name ? name : out_mbtiles ? out_mbtiles : out_directory, maxzoom, minzoom, basezoom, basezoom_marker_width, outdb, out_directory, &exclude, &include, exclude_all, droprate, buffer, tmpdir, gamma, read_parallel, forcetable, attribution, gamma != 0, file_bbox, description, guess_maxzoom, &attribute_types); if (outdb != NULL) { mbtiles_close(outdb, argv); diff --git a/man/tippecanoe.1 b/man/tippecanoe.1 index 578af6c..9d38836 100644 --- a/man/tippecanoe.1 +++ b/man/tippecanoe.1 @@ -144,6 +144,12 @@ resolution is obtained than by using a smaller \fImaxzoom\fP or \fIdetail\fP\&. \fB\fC\-y\fR \fIname\fP or \fB\fC\-\-include=\fR\fIname\fP: Include the named properties in all features, excluding all those not explicitly named .IP \(bu 2 \fB\fC\-X\fR or \fB\fC\-\-exclude\-all\fR: Exclude all properties and encode only geometries +.IP \(bu 2 +\fB\fC\-T\fR\fIattribute\fP\fB\fC:\fR\fItype\fP or \fB\fC\-\-attribute\-type=\fR\fIattribute\fP\fB\fC:\fR\fItype\fP: Coerce the named feature \fIattribute\fP to be of the specified \fItype\fP\&. +The \fItype\fP may be \fB\fCstring\fR, \fB\fCfloat\fR, \fB\fCint\fR, or \fB\fCbool\fR\&. +If the type is \fB\fCbool\fR, then original attributes of \fB\fC0\fR, \fB\fCfalse\fR, \fB\fCnull\fR, or the empty string become \fB\fCfalse\fR, and otherwise become \fB\fCtrue\fR\&. +If the type is \fB\fCfloat\fR or \fB\fCint\fR and the original attribute was non\-numeric, it becomes \fB\fC0\fR\&. +If the type is \fB\fCint\fR and the original attribute was floating\-point, it is rounded to the nearest integer. .RE .SS Dropping a fixed fraction of features by zoom level .RS diff --git a/tests/attribute-type/in.json b/tests/attribute-type/in.json new file mode 100644 index 0000000..9744fe3 --- /dev/null +++ b/tests/attribute-type/in.json @@ -0,0 +1,39 @@ +{ "type": "Feature", "properties": { "booltype": null, "expect": false }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } } +{ "type": "Feature", "properties": { "booltype": "null", "expect": false }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } } +{ "type": "Feature", "properties": { "booltype": "", "expect": false }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } } +{ "type": "Feature", "properties": { "booltype": 0, "expect": false }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } } +{ "type": "Feature", "properties": { "booltype": "0", "expect": false }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } } +{ "type": "Feature", "properties": { "booltype": false, "expect": false }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } } +{ "type": "Feature", "properties": { "booltype": "false", "expect": false }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } } +{ "type": "Feature", "properties": { "booltype": true, "expect": true }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } } +{ "type": "Feature", "properties": { "booltype": 1, "expect": true }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } } +{ "type": "Feature", "properties": { "booltype": 0.0, "expect": true }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } } +{ "type": "Feature", "properties": { "booltype": "yes", "expect": true }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } } +{ "type": "Feature", "properties": { "booltype": [ 2, 3 ], "expect": true }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } } +{ "type": "Feature", "properties": { "stringtype": null, "expect": "null" }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } } +{ "type": "Feature", "properties": { "stringtype": 2, "expect": "2" }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } } +{ "type": "Feature", "properties": { "stringtype": 2.5, "expect": "2.5" }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } } +{ "type": "Feature", "properties": { "stringtype": true, "expect": "true" }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } } +{ "type": "Feature", "properties": { "stringtype": false, "expect": "false" }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } } +{ "type": "Feature", "properties": { "stringtype": "something", "expect": "something" }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } } +{ "type": "Feature", "properties": { "stringtype": [ 2, 3 ], "expect": "[2,3]" }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } } +{ "type": "Feature", "properties": { "inttype": null, "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } } +{ "type": "Feature", "properties": { "inttype": "5", "expect": 5 }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } } +{ "type": "Feature", "properties": { "inttype": "5.6", "expect": 6 }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } } +{ "type": "Feature", "properties": { "inttype": 5.6, "expect": 6 }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } } +{ "type": "Feature", "properties": { "inttype": true, "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } } +{ "type": "Feature", "properties": { "inttype": false, "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } } +{ "type": "Feature", "properties": { "inttype": "", "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } } +{ "type": "Feature", "properties": { "inttype": "yes", "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } } +{ "type": "Feature", "properties": { "inttype": " 3", "expect": 3 }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } } +{ "type": "Feature", "properties": { "inttype": [ 2, 3 ], "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } } +{ "type": "Feature", "properties": { "floattype": null, "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } } +{ "type": "Feature", "properties": { "floattype": "5", "expect": 5 }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } } +{ "type": "Feature", "properties": { "floattype": "5.6", "expect": 5.6 }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } } +{ "type": "Feature", "properties": { "floattype": 5.6, "expect": 5.6 }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } } +{ "type": "Feature", "properties": { "floattype": true, "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } } +{ "type": "Feature", "properties": { "floattype": false, "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } } +{ "type": "Feature", "properties": { "floattype": "", "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } } +{ "type": "Feature", "properties": { "floattype": "yes", "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } } +{ "type": "Feature", "properties": { "floattype": " 3", "expect": 3 }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } } +{ "type": "Feature", "properties": { "floattype": [ 2, 3 ], "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } } diff --git a/tests/attribute-type/out/-z0_-Tinttype@int_-Tfloattype@float_-Tbooltype@bool_-Tstringtype@string.json b/tests/attribute-type/out/-z0_-Tinttype@int_-Tfloattype@float_-Tbooltype@bool_-Tstringtype@string.json new file mode 100644 index 0000000..2be4ed0 --- /dev/null +++ b/tests/attribute-type/out/-z0_-Tinttype@int_-Tfloattype@float_-Tbooltype@bool_-Tstringtype@string.json @@ -0,0 +1,94 @@ +{ "type": "FeatureCollection", "properties": { +"bounds": "0.000000,0.000000,0.000000,0.000000", +"center": "0.000000,0.000000,0", +"description": "tests/attribute-type/out/-z0_-Tinttype@int_-Tfloattype@float_-Tbooltype@bool_-Tstringtype@string.json.check.mbtiles", +"format": "pbf", +"json": "{\"vector_layers\": [ { \"id\": \"in\", \"description\": \"\", \"minzoom\": 0, \"maxzoom\": 0, \"fields\": {\"booltype\": \"Boolean\", \"expect\": \"String\", \"expect\": \"Number\", \"expect\": \"Boolean\", \"floattype\": \"Number\", \"inttype\": \"Number\", \"stringtype\": \"String\"} } ] }", +"maxzoom": "0", +"minzoom": "0", +"name": "tests/attribute-type/out/-z0_-Tinttype@int_-Tfloattype@float_-Tbooltype@bool_-Tstringtype@string.json.check.mbtiles", +"type": "overlay", +"version": "2" +}, "features": [ +{ "type": "FeatureCollection", "properties": { "zoom": 0, "x": 0, "y": 0 }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "in", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "booltype": false, "expect": false }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } } +, +{ "type": "Feature", "properties": { "booltype": false, "expect": false }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } } +, +{ "type": "Feature", "properties": { "booltype": false, "expect": false }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } } +, +{ "type": "Feature", "properties": { "booltype": false, "expect": false }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } } +, +{ "type": "Feature", "properties": { "booltype": false, "expect": false }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } } +, +{ "type": "Feature", "properties": { "booltype": false, "expect": false }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } } +, +{ "type": "Feature", "properties": { "booltype": false, "expect": false }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } } +, +{ "type": "Feature", "properties": { "booltype": true, "expect": true }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } } +, +{ "type": "Feature", "properties": { "booltype": true, "expect": true }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } } +, +{ "type": "Feature", "properties": { "booltype": true, "expect": true }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } } +, +{ "type": "Feature", "properties": { "booltype": true, "expect": true }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } } +, +{ "type": "Feature", "properties": { "booltype": true, "expect": true }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } } +, +{ "type": "Feature", "properties": { "stringtype": "null", "expect": "null" }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } } +, +{ "type": "Feature", "properties": { "stringtype": "2", "expect": "2" }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } } +, +{ "type": "Feature", "properties": { "stringtype": "2.5", "expect": "2.5" }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } } +, +{ "type": "Feature", "properties": { "stringtype": "true", "expect": "true" }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } } +, +{ "type": "Feature", "properties": { "stringtype": "false", "expect": "false" }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } } +, +{ "type": "Feature", "properties": { "stringtype": "something", "expect": "something" }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } } +, +{ "type": "Feature", "properties": { "stringtype": "[2,3]", "expect": "[2,3]" }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } } +, +{ "type": "Feature", "properties": { "inttype": 0, "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } } +, +{ "type": "Feature", "properties": { "inttype": 5, "expect": 5 }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } } +, +{ "type": "Feature", "properties": { "inttype": 6, "expect": 6 }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } } +, +{ "type": "Feature", "properties": { "inttype": 6, "expect": 6 }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } } +, +{ "type": "Feature", "properties": { "inttype": 0, "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } } +, +{ "type": "Feature", "properties": { "inttype": 0, "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } } +, +{ "type": "Feature", "properties": { "inttype": 0, "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } } +, +{ "type": "Feature", "properties": { "inttype": 0, "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } } +, +{ "type": "Feature", "properties": { "inttype": 3, "expect": 3 }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } } +, +{ "type": "Feature", "properties": { "inttype": 0, "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } } +, +{ "type": "Feature", "properties": { "floattype": 0, "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } } +, +{ "type": "Feature", "properties": { "floattype": 5, "expect": 5 }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } } +, +{ "type": "Feature", "properties": { "floattype": 5.6, "expect": 5.6 }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } } +, +{ "type": "Feature", "properties": { "floattype": 5.6, "expect": 5.6 }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } } +, +{ "type": "Feature", "properties": { "floattype": 0, "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } } +, +{ "type": "Feature", "properties": { "floattype": 0, "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } } +, +{ "type": "Feature", "properties": { "floattype": 0, "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } } +, +{ "type": "Feature", "properties": { "floattype": 0, "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } } +, +{ "type": "Feature", "properties": { "floattype": 3, "expect": 3 }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } } +, +{ "type": "Feature", "properties": { "floattype": 0, "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } } +] } +] } +] } diff --git a/version.hpp b/version.hpp index fdb5e71..4bd4f19 100644 --- a/version.hpp +++ b/version.hpp @@ -1 +1 @@ -#define VERSION "tippecanoe v1.17.0\n" +#define VERSION "tippecanoe v1.17.1\n"