diff --git a/write_json.cpp b/write_json.cpp index 74c5ed3..b8f6ddb 100644 --- a/write_json.cpp +++ b/write_json.cpp @@ -9,6 +9,168 @@ #include "write_json.hpp" #include "milo/dtoa_milo.h" +enum json_write_tok { + JSON_WRITE_HASH, + JSON_WRITE_HASH_KEY, + JSON_WRITE_HASH_VALUE, + JSON_WRITE_ARRAY, + JSON_WRITE_ARRAY_ELEMENT, + JSON_WRITE_TOP, +}; + +struct json_write_state { + std::vector state; + + ~json_write_state() { + if (state.size() > 0) { + if (state.size() != 1 || state[0] != JSON_WRITE_TOP) { + fprintf(stderr, "JSON not closed at end\n"); + exit(EXIT_FAILURE); + } + } + } +}; + +static void json_adjust(FILE *f, json_write_state &state) { + if (state.state.size() == 0) { + state.state.push_back(JSON_WRITE_TOP); + } else if (state.state[state.state.size() - 1] == JSON_WRITE_TOP) { + fprintf(f, "\n"); + state.state[state.state.size() - 1] = JSON_WRITE_TOP; + } else if (state.state[state.state.size() - 1] == JSON_WRITE_HASH) { + fprintf(f, " "); + state.state[state.state.size() - 1] = JSON_WRITE_HASH_KEY; + } else if (state.state[state.state.size() - 1] == JSON_WRITE_HASH_KEY) { + fprintf(f, ": "); + state.state[state.state.size() - 1] = JSON_WRITE_HASH_VALUE; + } else if (state.state[state.state.size() - 1] == JSON_WRITE_HASH_VALUE) { + fprintf(f, ", "); + state.state[state.state.size() - 1] = JSON_WRITE_HASH_KEY; + } else if (state.state[state.state.size() - 1] == JSON_WRITE_ARRAY) { + fprintf(f, " "); + state.state[state.state.size() - 1] = JSON_WRITE_ARRAY_ELEMENT; + } else if (state.state[state.state.size() - 1] == JSON_WRITE_ARRAY_ELEMENT) { + fprintf(f, ", "); + state.state[state.state.size() - 1] = JSON_WRITE_ARRAY_ELEMENT; + } else { + fprintf(stderr, "Impossible JSON state\n"); + exit(EXIT_FAILURE); + } +} + +void json_write_array(FILE *f, json_write_state &state) { + json_adjust(f, state); + fprintf(f, "["); + + state.state.push_back(JSON_WRITE_ARRAY); +} + +void json_end_array(FILE *f, json_write_state &state) { + if (state.state.size() == 0) { + fprintf(stderr, "End JSON array at top level\n"); + exit(EXIT_FAILURE); + } + + json_write_tok tok = state.state[state.state.size() - 1]; + state.state.pop_back(); + + if (tok == JSON_WRITE_ARRAY || tok == JSON_WRITE_ARRAY_ELEMENT) { + fprintf(f, " ]"); + } else { + fprintf(stderr, "End JSON array with unexpected state\n"); + exit(EXIT_FAILURE); + } +} + +void json_write_hash(FILE *f, json_write_state &state) { + json_adjust(f, state); + fprintf(f, "{"); + + state.state.push_back(JSON_WRITE_HASH); +} + +void json_end_hash(FILE *f, json_write_state &state) { + if (state.state.size() == 0) { + fprintf(stderr, "End JSON hash at top level\n"); + exit(EXIT_FAILURE); + } + + json_write_tok tok = state.state[state.state.size() - 1]; + state.state.pop_back(); + + if (tok == JSON_WRITE_HASH) { + fprintf(f, " }"); // Preserve accidental extra space from before + } else if (tok == JSON_WRITE_HASH_VALUE) { + fprintf(f, " }"); + } else { + fprintf(stderr, "End JSON hash with unexpected state\n"); + exit(EXIT_FAILURE); + } +} + +void json_write_string(FILE *f, std::string const &s, json_write_state &state) { + json_adjust(f, state); + + putc('"', f); + for (size_t i = 0; i < s.size(); i++) { + if (s[i] == '\\' || s[i] == '"') { + fprintf(f, "\\%c", s[i]); + } else if ((unsigned char) s[i] < ' ') { + fprintf(f, "\\u%04x", s[i]); + } else { + putc(s[i], f); + } + } + putc('"', f); +} + +void json_write_number(FILE *f, double d, json_write_state &state) { + json_adjust(f, state); + + fputs(milo::dtoa_milo(d).c_str(), f); +} + +// Just to avoid changing expected output format +void json_write_float(FILE *f, double d, json_write_state &state) { + json_adjust(f, state); + + fprintf(f, "%f", d); +} + +void json_write_unsigned(FILE *f, unsigned long long v, json_write_state &state) { + json_adjust(f, state); + + fprintf(f, "%llu", v); +} + +void json_write_signed(FILE *f, long long v, json_write_state &state) { + json_adjust(f, state); + + fprintf(f, "%lld", v); +} + +void json_write_stringified(FILE *f, std::string const &s, json_write_state &state) { + json_adjust(f, state); + + fputs(s.c_str(), f); +} + +void json_write_bool(FILE *f, bool b, json_write_state &state) { + json_adjust(f, state); + + if (b) { + fputs("true", f); + } else { + fputs("false", f); + } +} + +void json_write_null(FILE *f, json_write_state &state) { + json_adjust(f, state); + + fputs("null", f); +} + struct lonlat { int op; double lon; @@ -26,84 +188,68 @@ struct lonlat { }; void layer_to_geojson(FILE *fp, mvt_layer const &layer, unsigned z, unsigned x, unsigned y, bool comma, bool name, bool zoom, bool dropped, unsigned long long index, long long sequence, long long extent, bool complain) { + json_write_state state; + for (size_t f = 0; f < layer.features.size(); f++) { mvt_feature const &feat = layer.features[f]; if (comma && f != 0) { - fprintf(fp, ",\n"); + fprintf(fp, ","); } - fprintf(fp, "{ \"type\": \"Feature\""); + json_write_hash(fp, state); + json_write_string(fp, "type", state); + json_write_string(fp, "Feature", state); if (feat.has_id) { - fprintf(fp, ", \"id\": %llu", feat.id); + json_write_string(fp, "id", state); + json_write_unsigned(fp, feat.id, state); } if (name || zoom || index != 0 || sequence != 0 || extent != 0) { - bool need_comma = false; - - fprintf(fp, ", \"tippecanoe\": { "); + json_write_string(fp, "tippecanoe", state); + json_write_hash(fp, state); if (name) { - if (need_comma) { - fprintf(fp, ", "); - } - fprintf(fp, "\"layer\": "); - fprintq(fp, layer.name.c_str()); - need_comma = true; + json_write_string(fp, "layer", state); + json_write_string(fp, layer.name, state); } if (zoom) { - if (need_comma) { - fprintf(fp, ", "); - } - fprintf(fp, "\"minzoom\": %u, ", z); - fprintf(fp, "\"maxzoom\": %u", z); - need_comma = true; + json_write_string(fp, "minzoom", state); + json_write_unsigned(fp, z, state); + + json_write_string(fp, "maxzoom", state); + json_write_unsigned(fp, z, state); } if (dropped) { - if (need_comma) { - fprintf(fp, ", "); - } - fprintf(fp, "\"dropped\": %s", feat.dropped ? "true" : "false"); - need_comma = true; + json_write_string(fp, "dropped", state); + json_write_bool(fp, feat.dropped, state); } if (index != 0) { - if (need_comma) { - fprintf(fp, ", "); - } - fprintf(fp, "\"index\": %llu", index); - need_comma = true; + json_write_string(fp, "index", state); + json_write_unsigned(fp, index, state); } if (sequence != 0) { - if (need_comma) { - fprintf(fp, ", "); - } - fprintf(fp, "\"sequence\": %lld", sequence); - need_comma = true; + json_write_string(fp, "sequence", state); + json_write_signed(fp, sequence, state); } if (extent != 0) { - if (need_comma) { - fprintf(fp, ", "); - } - fprintf(fp, "\"extent\": %lld", extent); - need_comma = true; + json_write_string(fp, "extent", state); + json_write_signed(fp, extent, state); } - fprintf(fp, " }"); + json_end_hash(fp, state); } - fprintf(fp, ", \"properties\": { "); + json_write_string(fp, "properties", state); + json_write_hash(fp, state); for (size_t t = 0; t + 1 < feat.tags.size(); t += 2) { - if (t != 0) { - fprintf(fp, ", "); - } - if (feat.tags[t] >= layer.keys.size()) { fprintf(stderr, "Error: out of bounds feature key (%u in %zu)\n", feat.tags[t], layer.keys.size()); exit(EXIT_FAILURE); @@ -117,33 +263,33 @@ void layer_to_geojson(FILE *fp, mvt_layer const &layer, unsigned z, unsigned x, mvt_value const &val = layer.values[feat.tags[t + 1]]; if (val.type == mvt_string) { - fprintq(fp, key); - fprintf(fp, ": "); - fprintq(fp, val.string_value.c_str()); + json_write_string(fp, key, state); + json_write_string(fp, val.string_value, state); } else if (val.type == mvt_int) { - fprintq(fp, key); - fprintf(fp, ": %lld", val.numeric_value.int_value); + json_write_string(fp, key, state); + json_write_signed(fp, val.numeric_value.int_value, state); } else if (val.type == mvt_double) { - fprintq(fp, key); - double v = val.numeric_value.double_value; - fprintf(fp, ": %s", milo::dtoa_milo(v).c_str()); + json_write_string(fp, key, state); + json_write_number(fp, val.numeric_value.double_value, state); } else if (val.type == mvt_float) { - fprintq(fp, key); - double v = val.numeric_value.float_value; - fprintf(fp, ": %s", milo::dtoa_milo(v).c_str()); + json_write_string(fp, key, state); + json_write_number(fp, val.numeric_value.float_value, state); } else if (val.type == mvt_sint) { - fprintq(fp, key); - fprintf(fp, ": %lld", val.numeric_value.sint_value); + json_write_string(fp, key, state); + json_write_signed(fp, val.numeric_value.sint_value, state); } else if (val.type == mvt_uint) { - fprintq(fp, key); - fprintf(fp, ": %llu", val.numeric_value.uint_value); + json_write_string(fp, key, state); + json_write_unsigned(fp, val.numeric_value.uint_value, state); } else if (val.type == mvt_bool) { - fprintq(fp, key); - fprintf(fp, ": %s", val.numeric_value.bool_value ? "true" : "false"); + json_write_string(fp, key, state); + json_write_bool(fp, val.numeric_value.bool_value, state); } } - fprintf(fp, " }, \"geometry\": { "); + json_end_hash(fp, state); + + json_write_string(fp, "geometry", state); + json_write_hash(fp, state); std::vector ops; @@ -168,16 +314,30 @@ void layer_to_geojson(FILE *fp, mvt_layer const &layer, unsigned z, unsigned x, if (feat.type == VT_POINT) { if (ops.size() == 1) { - fprintf(fp, "\"type\": \"Point\", \"coordinates\": [ %f, %f ]", ops[0].lon, ops[0].lat); + json_write_string(fp, "type", state); + json_write_string(fp, "Point", state); + + json_write_string(fp, "coordinates", state); + + json_write_array(fp, state); + json_write_float(fp, ops[0].lon, state); + json_write_float(fp, ops[0].lat, state); + json_end_array(fp, state); } else { - fprintf(fp, "\"type\": \"MultiPoint\", \"coordinates\": [ "); + json_write_string(fp, "type", state); + json_write_string(fp, "MultiPoint", state); + + json_write_string(fp, "coordinates", state); + json_write_array(fp, state); + for (size_t i = 0; i < ops.size(); i++) { - if (i != 0) { - fprintf(fp, ", "); - } - fprintf(fp, "[ %f, %f ]", ops[i].lon, ops[i].lat); + json_write_array(fp, state); + json_write_float(fp, ops[i].lon, state); + json_write_float(fp, ops[i].lat, state); + json_end_array(fp, state); } - fprintf(fp, " ]"); + + json_end_array(fp, state); } } else if (feat.type == VT_LINE) { int movetos = 0; @@ -188,32 +348,59 @@ void layer_to_geojson(FILE *fp, mvt_layer const &layer, unsigned z, unsigned x, } if (movetos < 2) { - fprintf(fp, "\"type\": \"LineString\", \"coordinates\": [ "); + json_write_string(fp, "type", state); + json_write_string(fp, "LineString", state); + + json_write_string(fp, "coordinates", state); + json_write_array(fp, state); + for (size_t i = 0; i < ops.size(); i++) { - if (i != 0) { - fprintf(fp, ", "); - } - fprintf(fp, "[ %f, %f ]", ops[i].lon, ops[i].lat); + json_write_array(fp, state); + json_write_float(fp, ops[i].lon, state); + json_write_float(fp, ops[i].lat, state); + json_end_array(fp, state); } - fprintf(fp, " ]"); + + json_end_array(fp, state); } else { - fprintf(fp, "\"type\": \"MultiLineString\", \"coordinates\": [ [ "); - int state = 0; + json_write_string(fp, "type", state); + json_write_string(fp, "MultiLineString", state); + + json_write_string(fp, "coordinates", state); + json_write_array(fp, state); + json_write_array(fp, state); + + int sstate = 0; for (size_t i = 0; i < ops.size(); i++) { if (ops[i].op == VT_MOVETO) { - if (state == 0) { - fprintf(fp, "[ %f, %f ]", ops[i].lon, ops[i].lat); - state = 1; + if (sstate == 0) { + json_write_array(fp, state); + json_write_float(fp, ops[i].lon, state); + json_write_float(fp, ops[i].lat, state); + json_end_array(fp, state); + + sstate = 1; } else { - fprintf(fp, " ], [ "); - fprintf(fp, "[ %f, %f ]", ops[i].lon, ops[i].lat); - state = 1; + json_end_array(fp, state); + json_write_array(fp, state); + + json_write_array(fp, state); + json_write_float(fp, ops[i].lon, state); + json_write_float(fp, ops[i].lat, state); + json_end_array(fp, state); + + sstate = 1; } } else { - fprintf(fp, ", [ %f, %f ]", ops[i].lon, ops[i].lat); + json_write_array(fp, state); + json_write_float(fp, ops[i].lon, state); + json_write_float(fp, ops[i].lat, state); + json_end_array(fp, state); } } - fprintf(fp, " ] ]"); + + json_end_array(fp, state); + json_end_array(fp, state); } } else if (feat.type == VT_POLYGON) { std::vector > rings; @@ -271,12 +458,23 @@ void layer_to_geojson(FILE *fp, mvt_layer const &layer, unsigned z, unsigned x, } if (outer > 1) { - fprintf(fp, "\"type\": \"MultiPolygon\", \"coordinates\": [ [ [ "); + json_write_string(fp, "type", state); + json_write_string(fp, "MultiPolygon", state); + + json_write_string(fp, "coordinates", state); + json_write_array(fp, state); + json_write_array(fp, state); + json_write_array(fp, state); } else { - fprintf(fp, "\"type\": \"Polygon\", \"coordinates\": [ [ "); + json_write_string(fp, "type", state); + json_write_string(fp, "Polygon", state); + + json_write_string(fp, "coordinates", state); + json_write_array(fp, state); + json_write_array(fp, state); } - int state = 0; + int sstate = 0; for (size_t i = 0; i < rings.size(); i++) { if (i == 0 && areas[i] < 0) { static bool warned = false; @@ -292,45 +490,61 @@ void layer_to_geojson(FILE *fp, mvt_layer const &layer, unsigned z, unsigned x, } if (areas[i] >= 0) { - if (state != 0) { + if (sstate != 0) { // new multipolygon - fprintf(fp, " ] ], [ [ "); + json_end_array(fp, state); + json_end_array(fp, state); + + json_write_array(fp, state); + json_write_array(fp, state); } - state = 1; + sstate = 1; } - if (state == 2) { + if (sstate == 2) { // new ring in the same polygon - fprintf(fp, " ], [ "); + json_end_array(fp, state); + json_write_array(fp, state); } for (size_t j = 0; j < rings[i].size(); j++) { if (rings[i][j].op != VT_CLOSEPATH) { - if (j != 0) { - fprintf(fp, ", "); - } - - fprintf(fp, "[ %f, %f ]", rings[i][j].lon, rings[i][j].lat); + json_write_array(fp, state); + json_write_float(fp, rings[i][j].lon, state); + json_write_float(fp, rings[i][j].lat, state); + json_end_array(fp, state); } else { - if (j != 0) { - fprintf(fp, ", "); - } - - fprintf(fp, "[ %f, %f ]", rings[i][0].lon, rings[i][0].lat); + json_write_array(fp, state); + json_write_float(fp, rings[i][0].lon, state); + json_write_float(fp, rings[i][0].lat, state); + json_end_array(fp, state); } } - state = 2; + sstate = 2; } if (outer > 1) { - fprintf(fp, " ] ] ]"); + json_end_array(fp, state); + json_end_array(fp, state); + json_end_array(fp, state); } else { - fprintf(fp, " ] ]"); + json_end_array(fp, state); + json_end_array(fp, state); } } - fprintf(fp, " } }\n"); + json_end_hash(fp, state); + json_end_hash(fp, state); + + if (comma) { + fprintf(fp, "\n"); + } + } + + // XXX clean up newlines + if (!comma) { + fprintf(fp, "\n"); } }