diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 000000000..30cf57ed7 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 000000000..bdb487351 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/tippecanoe-cartesian.iml b/.idea/tippecanoe-cartesian.iml new file mode 100644 index 000000000..e69de29bb diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 000000000..e69de29bb diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..dbe981954 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,61 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Build & Test + +```bash +make -j # build all binaries +make test # full test suite (includes geobuf) +make fewer-tests # faster subset (skips geobuf) +make indent # clang-format all source +make clean # remove build artifacts +make install # install to /usr/local (override with PREFIX=...) +``` + +Build flags: `BUILDTYPE=Debug` (default Release), custom `CC`, `CXX`, `CFLAGS`, `CXXFLAGS`, `LDFLAGS`. + +Unit tests run via `./unit` (built from `unit.cpp`, tests `text.cpp`). + +## What This Is + +C++11 geospatial toolchain that converts GeoJSON/Geobuf/CSV into Mapbox Vector Tile (MVT) tilesets stored as MBTiles (SQLite) or directory trees. Core insight: at low zoom, drop *least-visible* features rather than simplifying geometry—preserving data density and regional texture. + +## Executables + +| Binary | Entry point | Purpose | +|--------|-------------|---------| +| `tippecanoe` | `main.cpp` | Main feature→tileset pipeline | +| `tippecanoe-decode` | `decode.cpp` | MBTiles/PBF → GeoJSON | +| `tippecanoe-enumerate` | `enumerate.cpp` | List tiles in an MBTiles | +| `tile-join` | `tile-join.cpp` | Merge/filter/subset MBTiles | +| `tippecanoe-json-tool` | `jsontool.cpp` | JSON sorting/CSV integration | +| `unit` | `unit.cpp` | Unit test runner | + +## Architecture + +**Processing pipeline** (all in `main.cpp`): +``` +CLI parse → read input (geojson/geobuf/csv) → serialize features to temp files +→ build spatial index → traverse_zooms() → write_tile() per zoom +→ encode MVT/PBF → write MBTiles (mbtiles.cpp) or dirtiles (dirtiles.cpp) +``` + +**Key module responsibilities:** +- `geometry.cpp/hpp` — coordinate transforms, drawing command encoding, simplification +- `tile.cpp/hpp` — zoom traversal, per-tile feature collection +- `mvt.cpp/hpp` — Mapbox Vector Tile PBF encoding +- `mbtiles.cpp/hpp` — SQLite MBTiles I/O +- `serial.cpp/hpp` — feature serialization between pipeline stages +- `evaluator.cpp/hpp` — filter expression evaluation +- `projection.cpp` — Web Mercator and custom projections +- `jsonpull/` — streaming JSON parser (embedded library) +- `geojson.cpp`, `geobuf.cpp`, `geocsv.cpp` — input format parsers + +**Limits** (defined in `main.hpp`): `MAX_ZOOM=24`, default tile max 500KB, max 200K features/tile. + +**Parallelism**: `-P` flag enables multi-file parallel processing; uses `CPUS` (detected core count) and temp files for overflow. + +## Test Structure + +`tests/` holds 40+ fixture directories. Each test compares output tiles/JSON against a `stable/` baseline. To add a test, create a directory with input files and update the relevant test target in `Makefile`. diff --git a/Makefile b/Makefile index ae542a1f1..507806d14 100644 --- a/Makefile +++ b/Makefile @@ -82,7 +82,7 @@ indent: TESTS = $(wildcard tests/*/out/*.json) SPACE = $(NULL) $(NULL) -test: tippecanoe tippecanoe-decode $(addsuffix .check,$(TESTS)) raw-tiles-test parallel-test pbf-test join-test enumerate-test decode-test join-filter-test unit json-tool-test allow-existing-test csv-test layer-json-test +test: tippecanoe tippecanoe-decode $(addsuffix .check,$(TESTS)) raw-tiles-test parallel-test pbf-test join-test enumerate-test decode-test join-filter-test unit json-tool-test allow-existing-test csv-test layer-json-test cartesian-test ./unit suffixes = json json.gz @@ -100,7 +100,17 @@ nogeobuf = tests/overflow/out/-z0.json $(wildcard tests/stringid/out/*.json) geobuf-test: tippecanoe-json-tool $(addsuffix .checkbuf,$(filter-out $(nogeobuf),$(TESTS))) # For quicker address sanitizer build, hope that regular JSON parsing is tested enough by parallel and join tests -fewer-tests: tippecanoe tippecanoe-decode geobuf-test raw-tiles-test parallel-test pbf-test join-test enumerate-test decode-test join-filter-test unit +fewer-tests: tippecanoe tippecanoe-decode geobuf-test raw-tiles-test parallel-test pbf-test join-test enumerate-test decode-test join-filter-test unit cartesian-test + +cartesian-test: tippecanoe tippecanoe-decode + ./tippecanoe -q -z5 --cartesian --cartesian-extent=0,0,100,100 -f -n cartesian-points -o tests/cartesian/points.mbtiles.check tests/cartesian/points.json + ./tippecanoe-decode -x generator -x generator_options tests/cartesian/points.mbtiles.check > tests/cartesian/points.mbtiles.json.check + cmp tests/cartesian/points.mbtiles.json.check tests/cartesian/points.mbtiles.json + rm -f tests/cartesian/points.mbtiles.check tests/cartesian/points.mbtiles.json.check + ./tippecanoe -q -z3 --cartesian --cartesian-extent=-50,-50,50,50 -f -n cartesian-shapes -o tests/cartesian/shapes.mbtiles.check tests/cartesian/shapes.json + ./tippecanoe-decode -x generator -x generator_options tests/cartesian/shapes.mbtiles.check > tests/cartesian/shapes.mbtiles.json.check + cmp tests/cartesian/shapes.mbtiles.json.check tests/cartesian/shapes.mbtiles.json + rm -f tests/cartesian/shapes.mbtiles.check tests/cartesian/shapes.mbtiles.json.check # XXX Use proper makefile rules instead of a for loop %.json.checkbuf: diff --git a/README.md b/README.md index 40f0d32a2..b49301415 100644 --- a/README.md +++ b/README.md @@ -356,6 +356,22 @@ Parallel processing will also be automatic if the input file is in Geobuf format * `-s` _projection_ or `--projection=`_projection_: Specify the projection of the input data. Currently supported are `EPSG:4326` (WGS84, the default) and `EPSG:3857` (Web Mercator). In general you should use WGS84 for your input files if at all possible. +### Cartesian coordinate mode + + * `--cartesian`: Treat input coordinates as raw Cartesian x,y values instead of geographic longitude/latitude. Requires `--cartesian-extent`. + * `--cartesian-extent=`_minx_`,`_miny_`,`_maxx_`,`_maxy_: Define the coordinate space bounds for Cartesian mode. Coordinates outside this extent will be clipped. + +Use Cartesian mode for non-geographic data such as floor plans, game maps, or scientific coordinate systems. The projection is linear (no Mercator distortion). Aspect ratio is preserved — the larger dimension maps to the full tile space, and the shorter dimension is centered. + +`tippecanoe-decode` automatically detects Cartesian tilesets from the stored metadata and outputs the original Cartesian coordinates. The CRS is reported as `"cartesian"` in decoded output. Cannot be combined with `--projection`. + +Example: + +``` +tippecanoe --cartesian --cartesian-extent=0,0,1000,500 -z8 -f -o floor-plan.mbtiles floor-plan.json +tippecanoe-decode floor-plan.mbtiles # outputs Cartesian coordinates automatically +``` + ### Zoom levels * `-z` _zoom_ or `--maximum-zoom=`_zoom_: Maxzoom: the highest zoom level for which tiles are generated (default 14) @@ -372,31 +388,31 @@ or the map scale of a corresponding printed map, this table shows the approximate precision and scale corresponding to various `-z` options if you use the default `-d` detail of 12: -zoom level | precision (ft) | precision (m) | map scale ----------- | -------------- | ------------- | --------- -`-z0` | 32000 ft | 10000 m | 1:320,000,000 -`-z1` | 16000 ft | 5000 m | 1:160,000,000 -`-z2` | 8000 ft | 2500 m | 1:80,000,000 -`-z3` | 4000 ft | 1250 m | 1:40,000,000 -`-z4` | 2000 ft | 600 m | 1:20,000,000 -`-z5` | 1000 ft | 300 m | 1:10,000,000 -`-z6` | 500 ft | 150 m | 1:5,000,000 -`-z7` | 250 ft | 80 m | 1:2,500,000 -`-z8` | 125 ft | 40 m | 1:1,250,000 -`-z9` | 64 ft | 20 m | 1:640,000 -`-z10` | 32 ft | 10 m | 1:320,000 -`-z11` | 16 ft | 5 m | 1:160,000 -`-z12` | 8 ft | 2 m | 1:80,000 -`-z13` | 4 ft | 1 m | 1:40,000 -`-z14` | 2 ft | 0.5 m | 1:20,000 -`-z15` | 1 ft | 0.25 m | 1:10,000 -`-z16` | 6 in | 15 cm | 1:5000 -`-z17` | 3 in | 8 cm | 1:2500 -`-z18` | 1.5 in | 4 cm | 1:1250 -`-z19` | 0.8 in | 2 cm | 1:600 -`-z20` | 0.4 in | 1 cm | 1:300 -`-z21` | 0.2 in | 0.5 cm | 1:150 -`-z22` | 0.1 in | 0.25 cm | 1:75 +| zoom level | precision (ft) | precision (m) | map scale | +|------------|----------------|---------------|---------------| +| `-z0` | 32000 ft | 10000 m | 1:320,000,000 | +| `-z1` | 16000 ft | 5000 m | 1:160,000,000 | +| `-z2` | 8000 ft | 2500 m | 1:80,000,000 | +| `-z3` | 4000 ft | 1250 m | 1:40,000,000 | +| `-z4` | 2000 ft | 600 m | 1:20,000,000 | +| `-z5` | 1000 ft | 300 m | 1:10,000,000 | +| `-z6` | 500 ft | 150 m | 1:5,000,000 | +| `-z7` | 250 ft | 80 m | 1:2,500,000 | +| `-z8` | 125 ft | 40 m | 1:1,250,000 | +| `-z9` | 64 ft | 20 m | 1:640,000 | +| `-z10` | 32 ft | 10 m | 1:320,000 | +| `-z11` | 16 ft | 5 m | 1:160,000 | +| `-z12` | 8 ft | 2 m | 1:80,000 | +| `-z13` | 4 ft | 1 m | 1:40,000 | +| `-z14` | 2 ft | 0.5 m | 1:20,000 | +| `-z15` | 1 ft | 0.25 m | 1:10,000 | +| `-z16` | 6 in | 15 cm | 1:5000 | +| `-z17` | 3 in | 8 cm | 1:2500 | +| `-z18` | 1.5 in | 4 cm | 1:1250 | +| `-z19` | 0.8 in | 2 cm | 1:600 | +| `-z20` | 0.4 in | 1 cm | 1:300 | +| `-z21` | 0.2 in | 0.5 cm | 1:150 | +| `-z22` | 0.1 in | 0.25 cm | 1:75 | ### Tile resolution @@ -881,6 +897,8 @@ resolutions. ### Options * `-s` _projection_ or `--projection=`*projection*: Specify the projection of the output data. Currently supported are EPSG:4326 (WGS84, the default) and EPSG:3857 (Web Mercator). + * `--cartesian`: Decode using Cartesian coordinates. Automatically detected from tileset metadata when decoding an MBTiles file created with `--cartesian`. + * `--cartesian-extent=`_minx_`,`_miny_`,`_maxx_`,`_maxy_: Override the Cartesian extent for decoding. Useful when decoding standalone PBF tiles created with `--cartesian`. * `-z` _maxzoom_ or `--maximum-zoom=`*maxzoom*: Specify the highest zoom level to decode from the tileset * `-Z` _minzoom_ or `--minimum-zoom=`*minzoom*: Specify the lowest zoom level to decode from the tileset * `-l` _layer_ or `--layer=`*layer*: Decode only layers with the specified names. (Multiple `-l` options can be specified.) diff --git a/decode.cpp b/decode.cpp index 6a0b677ca..ca2452bf9 100644 --- a/decode.cpp +++ b/decode.cpp @@ -238,6 +238,10 @@ void decode(char *fname, int z, unsigned x, unsigned y, std::set co char *map = (char *) mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); if (map != NULL && map != MAP_FAILED) { if (strcmp(map, "SQLite format 3") != 0) { + if (cartesian_mode && !cartesian_extent_set) { + fprintf(stderr, "%s: --cartesian requires --cartesian-extent when decoding a PBF tile\n", fname); + exit(EXIT_FAILURE); + } if (z >= 0) { std::string s = std::string(map, st.st_size); handle(s, z, x, y, to_decode, pipeline, stats, state); @@ -282,6 +286,9 @@ void decode(char *fname, int z, unsigned x, unsigned y, std::set co } } + // Auto-detect Cartesian mode from MBTiles metadata (CLI flags override) + set_cartesian_from_metadata(db); + if (z < 0) { int within = 0; @@ -504,6 +511,8 @@ int main(int argc, char **argv) { {"stats", no_argument, 0, 'S'}, {"force", no_argument, 0, 'f'}, {"exclude-metadata-row", required_argument, 0, 'x'}, + {"cartesian", no_argument, 0, '~'}, + {"cartesian-extent", required_argument, 0, '~'}, {0, 0, 0, 0}, }; @@ -518,7 +527,8 @@ int main(int argc, char **argv) { } } - while ((i = getopt_long(argc, argv, getopt_str.c_str(), long_options, NULL)) != -1) { + int option_index = 0; + while ((i = getopt_long(argc, argv, getopt_str.c_str(), long_options, &option_index)) != -1) { switch (i) { case 0: break; @@ -555,6 +565,35 @@ int main(int argc, char **argv) { exclude_meta.insert(optarg); break; + case '~': { + const char *opt = long_options[option_index].name; + if (strcmp(opt, "cartesian") == 0) { + cartesian_mode = true; + projection = get_projection("cartesian"); + } else if (strcmp(opt, "cartesian-extent") == 0) { + if (sscanf(optarg, "%lf,%lf,%lf,%lf", + &cartesian_extent[0], &cartesian_extent[1], + &cartesian_extent[2], &cartesian_extent[3]) != 4) { + fprintf(stderr, "Can't parse Cartesian extent: %s\n", optarg); + exit(EXIT_FAILURE); + } + double width = cartesian_extent[2] - cartesian_extent[0]; + double height = cartesian_extent[3] - cartesian_extent[1]; + if (width <= 0 || height <= 0) { + fprintf(stderr, "--cartesian-extent must have positive width and height (got %g,%g,%g,%g)\n", + cartesian_extent[0], cartesian_extent[1], cartesian_extent[2], cartesian_extent[3]); + exit(EXIT_FAILURE); + } + cartesian_extent_set = true; + cartesian_mode = true; + projection = get_projection("cartesian"); + } else { + fprintf(stderr, "Unrecognized option: --%s\n", opt); + usage(argv); + } + break; + } + default: usage(argv); } diff --git a/main.cpp b/main.cpp index ae41d8254..1735e7624 100644 --- a/main.cpp +++ b/main.cpp @@ -1103,13 +1103,16 @@ void choose_first_zoom(long long *file_bbox, std::vector &readers // If the bounding box extends off the plane on either side, // a feature wrapped across the date line, so the width of the // bounding box is the whole world. - if (file_bbox[0] < 0) { - file_bbox[0] = 0; - file_bbox[2] = (1LL << 32) - 1; - } - if (file_bbox[2] > (1LL << 32) - 1) { - file_bbox[0] = 0; - file_bbox[2] = (1LL << 32) - 1; + // (Not applicable for Cartesian mode where coordinates don't wrap.) + if (!cartesian_mode) { + if (file_bbox[0] < 0) { + file_bbox[0] = 0; + file_bbox[2] = (1LL << 32) - 1; + } + if (file_bbox[2] > (1LL << 32) - 1) { + file_bbox[0] = 0; + file_bbox[2] = (1LL << 32) - 1; + } } if (file_bbox[1] < 0) { file_bbox[1] = 0; @@ -2300,14 +2303,14 @@ int read_input(std::vector &sources, char *fname, int maxzoom, int minzo double minlat = 0, minlon = 0, maxlat = 0, maxlon = 0, midlat = 0, midlon = 0; - tile2lonlat(midx, midy, maxzoom, &minlon, &maxlat); - tile2lonlat(midx + 1, midy + 1, maxzoom, &maxlon, &minlat); + projection->unproject(midx, midy, maxzoom, &minlon, &maxlat); + projection->unproject(midx + 1, midy + 1, maxzoom, &maxlon, &minlat); midlat = (maxlat + minlat) / 2; midlon = (maxlon + minlon) / 2; - tile2lonlat(file_bbox[0], file_bbox[1], 32, &minlon, &maxlat); - tile2lonlat(file_bbox[2], file_bbox[3], 32, &maxlon, &minlat); + projection->unproject(file_bbox[0], file_bbox[1], 32, &minlon, &maxlat); + projection->unproject(file_bbox[2], file_bbox[3], 32, &maxlon, &minlat); if (midlat < minlat) { midlat = minlat; @@ -2485,6 +2488,7 @@ int main(int argc, char **argv) { std::map attribute_descriptions; int exclude_all = 0; int read_parallel = 0; + bool projection_explicitly_set = false; int files_open_at_start; json_object *filter = NULL; @@ -2514,6 +2518,8 @@ int main(int argc, char **argv) { {"Projection of input", 0, 0, 0}, {"projection", required_argument, 0, 's'}, + {"cartesian", no_argument, 0, '~'}, + {"cartesian-extent", required_argument, 0, '~'}, {"Zoom levels", 0, 0, 0}, {"maximum-zoom", required_argument, 0, 'z'}, @@ -2697,6 +2703,27 @@ int main(int argc, char **argv) { } } else if (strcmp(opt, "use-attribute-for-id") == 0) { attribute_for_id = optarg; + } else if (strcmp(opt, "cartesian") == 0) { + if (projection_explicitly_set) { + fprintf(stderr, "%s: --cartesian cannot be combined with --projection/-s\n", argv[0]); + exit(EXIT_FAILURE); + } + cartesian_mode = true; + projection = get_projection("cartesian"); + } else if (strcmp(opt, "cartesian-extent") == 0) { + if (projection_explicitly_set) { + fprintf(stderr, "%s: --cartesian-extent cannot be combined with --projection/-s\n", argv[0]); + exit(EXIT_FAILURE); + } + if (sscanf(optarg, "%lf,%lf,%lf,%lf", + &cartesian_extent[0], &cartesian_extent[1], + &cartesian_extent[2], &cartesian_extent[3]) != 4) { + fprintf(stderr, "%s: Can't parse Cartesian extent --%s=%s\n", argv[0], opt, optarg); + exit(EXIT_FAILURE); + } + cartesian_extent_set = true; + cartesian_mode = true; + projection = get_projection("cartesian"); } else { fprintf(stderr, "%s: Unrecognized option --%s\n", argv[0], opt); exit(EXIT_FAILURE); @@ -2971,7 +2998,12 @@ int main(int argc, char **argv) { break; case 's': + if (cartesian_mode) { + fprintf(stderr, "%s: --projection/-s cannot be combined with --cartesian/--cartesian-extent\n", argv[0]); + exit(EXIT_FAILURE); + } set_projection_or_exit(optarg); + projection_explicitly_set = true; break; case 'S': @@ -3046,6 +3078,20 @@ int main(int argc, char **argv) { } } + if (cartesian_mode) { + if (!cartesian_extent_set) { + fprintf(stderr, "%s: --cartesian requires --cartesian-extent=minx,miny,maxx,maxy\n", argv[0]); + exit(EXIT_FAILURE); + } + double width = cartesian_extent[2] - cartesian_extent[0]; + double height = cartesian_extent[3] - cartesian_extent[1]; + if (width <= 0 || height <= 0) { + fprintf(stderr, "%s: --cartesian-extent must have positive width and height (got %g,%g,%g,%g)\n", + argv[0], cartesian_extent[0], cartesian_extent[1], cartesian_extent[2], cartesian_extent[3]); + exit(EXIT_FAILURE); + } + } + if (additional[A_HILBERT]) { encode_index = encode_hilbert; decode_index = decode_hilbert; diff --git a/mbtiles.cpp b/mbtiles.cpp index a430a72fa..3455a2b6c 100644 --- a/mbtiles.cpp +++ b/mbtiles.cpp @@ -14,6 +14,7 @@ #include #include "mvt.hpp" #include "mbtiles.hpp" +#include "projection.hpp" #include "text.hpp" #include "milo/dtoa_milo.h" #include "write_json.hpp" @@ -393,6 +394,28 @@ void mbtiles_write_metadata(sqlite3 *outdb, const char *outdir, const char *fnam } sqlite3_free(sql); + if (cartesian_mode) { + sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('cartesian', 'true');"); + if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) { + fprintf(stderr, "set cartesian: %s\n", err); + if (!forcetable) { + exit(EXIT_FAILURE); + } + } + sqlite3_free(sql); + + sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('cartesian_extent', '%.17g,%.17g,%.17g,%.17g');", + cartesian_extent[0], cartesian_extent[1], + cartesian_extent[2], cartesian_extent[3]); + if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) { + fprintf(stderr, "set cartesian_extent: %s\n", err); + if (!forcetable) { + exit(EXIT_FAILURE); + } + } + sqlite3_free(sql); + } + if (vector) { size_t elements = max_tilestats_values; std::string buf; diff --git a/projection.cpp b/projection.cpp index a29516a53..661dc1102 100644 --- a/projection.cpp +++ b/projection.cpp @@ -3,14 +3,20 @@ #include #include #include +#include #include "projection.hpp" unsigned long long (*encode_index)(unsigned int wx, unsigned int wy) = NULL; void (*decode_index)(unsigned long long index, unsigned *wx, unsigned *wy) = NULL; +bool cartesian_mode = false; +bool cartesian_extent_set = false; +double cartesian_extent[4] = {0, 0, 0, 0}; // minx, miny, maxx, maxy + struct projection projections[] = { {"EPSG:4326", lonlat2tile, tile2lonlat, "urn:ogc:def:crs:OGC:1.3:CRS84"}, {"EPSG:3857", epsg3857totile, tiletoepsg3857, "urn:ogc:def:crs:EPSG::3857"}, + {"cartesian", cartesian2tile, tile2cartesian, "cartesian"}, {NULL, NULL, NULL, NULL}, }; @@ -101,6 +107,45 @@ void tiletoepsg3857(long long ix, long long iy, int zoom, double *ox, double *oy *oy = ((1LL << 32) - 1 - iy - (1LL << 31)) * M_PI * 6378137.0 / (1LL << 31); } +void cartesian2tile(double ix, double iy, int zoom, long long *ox, long long *oy) { + double width = cartesian_extent[2] - cartesian_extent[0]; + double height = cartesian_extent[3] - cartesian_extent[1]; + double range = (width > height) ? width : height; + if (range == 0) { + range = 1; + } + double cx = (cartesian_extent[0] + cartesian_extent[2]) / 2.0; + double cy = (cartesian_extent[1] + cartesian_extent[3]) / 2.0; + + int ix_class = fpclassify(ix); + int iy_class = fpclassify(iy); + if (ix_class == FP_INFINITE || ix_class == FP_NAN) { + ix = cx; + } + if (iy_class == FP_INFINITE || iy_class == FP_NAN) { + iy = cy; + } + + unsigned long long n = 1LL << zoom; + *ox = (long long)(((ix - cx) / range + 0.5) * n); + *oy = (long long)((0.5 - (iy - cy) / range) * n); +} + +void tile2cartesian(long long ix, long long iy, int zoom, double *ox, double *oy) { + double width = cartesian_extent[2] - cartesian_extent[0]; + double height = cartesian_extent[3] - cartesian_extent[1]; + double range = (width > height) ? width : height; + if (range == 0) { + range = 1; + } + double cx = (cartesian_extent[0] + cartesian_extent[2]) / 2.0; + double cy = (cartesian_extent[1] + cartesian_extent[3]) / 2.0; + + unsigned long long n = 1LL << zoom; + *ox = ((double) ix / n - 0.5) * range + cx; + *oy = (0.5 - (double) iy / n) * range + cy; +} + // https://en.wikipedia.org/wiki/Hilbert_curve void hilbert_rot(unsigned long long n, unsigned *x, unsigned *y, unsigned long long rx, unsigned long long ry) { @@ -215,3 +260,44 @@ void set_projection_or_exit(const char *optarg) { exit(EXIT_FAILURE); } } + +struct projection *get_projection(const char *name) { + for (struct projection *p = projections; p->name != NULL; p++) { + if (strcmp(p->name, name) == 0) { + return p; + } + } + return NULL; +} + +void set_cartesian_from_metadata(sqlite3 *db) { + if (!cartesian_mode) { + sqlite3_stmt *stmt; + if (sqlite3_prepare_v2(db, "SELECT value FROM metadata WHERE name = 'cartesian'", -1, &stmt, NULL) == SQLITE_OK) { + if (sqlite3_step(stmt) == SQLITE_ROW) { + const unsigned char *val = sqlite3_column_text(stmt, 0); + if (val != NULL && strcmp((char *) val, "true") == 0) { + cartesian_mode = true; + projection = get_projection("cartesian"); + } + } + sqlite3_finalize(stmt); + } + } + if (cartesian_mode && !cartesian_extent_set) { + sqlite3_stmt *stmt; + if (sqlite3_prepare_v2(db, "SELECT value FROM metadata WHERE name = 'cartesian_extent'", -1, &stmt, NULL) == SQLITE_OK) { + if (sqlite3_step(stmt) == SQLITE_ROW) { + const unsigned char *val = sqlite3_column_text(stmt, 0); + if (val != NULL) { + if (sscanf((char *) val, "%lf,%lf,%lf,%lf", + &cartesian_extent[0], &cartesian_extent[1], + &cartesian_extent[2], &cartesian_extent[3]) == 4) { + cartesian_extent_set = true; + } + } + } + sqlite3_finalize(stmt); + } + } +} diff --git a/projection.hpp b/projection.hpp index d649ef56f..ba30d9347 100644 --- a/projection.hpp +++ b/projection.hpp @@ -5,7 +5,17 @@ void lonlat2tile(double lon, double lat, int zoom, long long *x, long long *y); void epsg3857totile(double ix, double iy, int zoom, long long *x, long long *y); void tile2lonlat(long long x, long long y, int zoom, double *lon, double *lat); void tiletoepsg3857(long long x, long long y, int zoom, double *ox, double *oy); +void cartesian2tile(double ix, double iy, int zoom, long long *ox, long long *oy); +void tile2cartesian(long long ix, long long iy, int zoom, double *ox, double *oy); void set_projection_or_exit(const char *optarg); +struct projection *get_projection(const char *name); + +struct sqlite3; +void set_cartesian_from_metadata(sqlite3 *db); + +extern bool cartesian_mode; +extern bool cartesian_extent_set; +extern double cartesian_extent[4]; // minx, miny, maxx, maxy struct projection { const char *name; diff --git a/tests/cartesian/points.json b/tests/cartesian/points.json new file mode 100644 index 000000000..d3ccda26e --- /dev/null +++ b/tests/cartesian/points.json @@ -0,0 +1,5 @@ +{ "type": "FeatureCollection", "features": [ + { "type": "Feature", "properties": {"id": 1}, "geometry": { "type": "Point", "coordinates": [10, 20] } }, + { "type": "Feature", "properties": {"id": 2}, "geometry": { "type": "Point", "coordinates": [50, 50] } }, + { "type": "Feature", "properties": {"id": 3}, "geometry": { "type": "Point", "coordinates": [90, 80] } } +]} diff --git a/tests/cartesian/points.mbtiles.json b/tests/cartesian/points.mbtiles.json new file mode 100644 index 000000000..f2e259e3a --- /dev/null +++ b/tests/cartesian/points.mbtiles.json @@ -0,0 +1,188 @@ +{ "type": "FeatureCollection", "properties": { +"bounds": "10.000000,20.000000,90.000000,80.000000", +"cartesian": "true", +"cartesian_extent": "0,0,100,100", +"center": "10.937500,20.312500,5", +"description": "cartesian-points", +"format": "pbf", +"json": "{\"vector_layers\": [ { \"id\": \"points\", \"description\": \"\", \"minzoom\": 0, \"maxzoom\": 5, \"fields\": {\"id\": \"Number\"} } ],\"tilestats\": {\"layerCount\": 1,\"layers\": [{\"layer\": \"points\",\"count\": 3,\"geometry\": \"Point\",\"attributeCount\": 1,\"attributes\": [{\"attribute\": \"id\",\"count\": 3,\"type\": \"number\",\"values\": [1,2,3],\"min\": 1,\"max\": 3}]}]}}", +"maxzoom": "5", +"minzoom": "0", +"name": "cartesian-points", +"type": "overlay", +"version": "2" +}, "features": [ +{ "type": "FeatureCollection", "properties": { "zoom": 0, "x": 0, "y": 0 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "points", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "id": 1 }, "geometry": { "type": "Point", "coordinates": [ 9.985352, 20.019531 ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 1, "x": 0, "y": 1 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "points", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "id": 1 }, "geometry": { "type": "Point", "coordinates": [ 9.997559, 20.007324 ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 1, "x": 0, "y": 0 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "points", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "id": 2 }, "geometry": { "type": "Point", "coordinates": [ 50.000000, 50.000000 ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 1, "x": 1, "y": 1 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "points", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "id": 2 }, "geometry": { "type": "Point", "coordinates": [ 50.000000, 50.000000 ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 1, "x": 1, "y": 0 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "points", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "id": 3 }, "geometry": { "type": "Point", "coordinates": [ 89.990234, 80.004883 ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 2, "x": 0, "y": 3 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "points", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "id": 1 }, "geometry": { "type": "Point", "coordinates": [ 9.997559, 20.001221 ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 2, "x": 1, "y": 2 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "points", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "id": 2 }, "geometry": { "type": "Point", "coordinates": [ 50.000000, 50.000000 ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 2, "x": 1, "y": 1 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "points", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "id": 2 }, "geometry": { "type": "Point", "coordinates": [ 50.000000, 50.000000 ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 2, "x": 2, "y": 2 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "points", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "id": 2 }, "geometry": { "type": "Point", "coordinates": [ 50.000000, 50.000000 ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 2, "x": 2, "y": 1 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "points", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "id": 2 }, "geometry": { "type": "Point", "coordinates": [ 50.000000, 50.000000 ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 2, "x": 3, "y": 0 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "points", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "id": 3 }, "geometry": { "type": "Point", "coordinates": [ 89.996338, 80.004883 ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 3, "x": 0, "y": 6 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "points", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "id": 1 }, "geometry": { "type": "Point", "coordinates": [ 9.997559, 20.001221 ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 3, "x": 3, "y": 4 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "points", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "id": 2 }, "geometry": { "type": "Point", "coordinates": [ 50.000000, 50.000000 ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 3, "x": 3, "y": 3 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "points", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "id": 2 }, "geometry": { "type": "Point", "coordinates": [ 50.000000, 50.000000 ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 3, "x": 4, "y": 4 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "points", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "id": 2 }, "geometry": { "type": "Point", "coordinates": [ 50.000000, 50.000000 ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 3, "x": 4, "y": 3 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "points", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "id": 2 }, "geometry": { "type": "Point", "coordinates": [ 50.000000, 50.000000 ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 3, "x": 7, "y": 1 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "points", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "id": 3 }, "geometry": { "type": "Point", "coordinates": [ 89.999390, 80.001831 ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 4, "x": 1, "y": 12 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "points", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "id": 1 }, "geometry": { "type": "Point", "coordinates": [ 9.999084, 20.001221 ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 4, "x": 7, "y": 8 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "points", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "id": 2 }, "geometry": { "type": "Point", "coordinates": [ 50.000000, 50.000000 ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 4, "x": 7, "y": 7 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "points", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "id": 2 }, "geometry": { "type": "Point", "coordinates": [ 50.000000, 50.000000 ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 4, "x": 8, "y": 8 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "points", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "id": 2 }, "geometry": { "type": "Point", "coordinates": [ 50.000000, 50.000000 ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 4, "x": 8, "y": 7 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "points", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "id": 2 }, "geometry": { "type": "Point", "coordinates": [ 50.000000, 50.000000 ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 4, "x": 14, "y": 3 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "points", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "id": 3 }, "geometry": { "type": "Point", "coordinates": [ 89.999390, 80.000305 ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 5, "x": 3, "y": 25 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "points", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "id": 1 }, "geometry": { "type": "Point", "coordinates": [ 9.999847, 20.000458 ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 5, "x": 15, "y": 16 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "points", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "id": 2 }, "geometry": { "type": "Point", "coordinates": [ 50.000000, 50.000000 ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 5, "x": 15, "y": 15 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "points", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "id": 2 }, "geometry": { "type": "Point", "coordinates": [ 50.000000, 50.000000 ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 5, "x": 16, "y": 16 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "points", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "id": 2 }, "geometry": { "type": "Point", "coordinates": [ 50.000000, 50.000000 ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 5, "x": 16, "y": 15 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "points", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "id": 2 }, "geometry": { "type": "Point", "coordinates": [ 50.000000, 50.000000 ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 5, "x": 28, "y": 6 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "points", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "id": 3 }, "geometry": { "type": "Point", "coordinates": [ 89.999390, 80.000305 ] } } +] } +] } +] } diff --git a/tests/cartesian/shapes.json b/tests/cartesian/shapes.json new file mode 100644 index 000000000..2c4e85eb5 --- /dev/null +++ b/tests/cartesian/shapes.json @@ -0,0 +1,4 @@ +{ "type": "FeatureCollection", "features": [ + { "type": "Feature", "properties": {"name": "line"}, "geometry": { "type": "LineString", "coordinates": [[0,0],[25,25],[50,0]] } }, + { "type": "Feature", "properties": {"name": "box"}, "geometry": { "type": "Polygon", "coordinates": [[[-10,-10],[10,-10],[10,10],[-10,10],[-10,-10]]] } } +]} diff --git a/tests/cartesian/shapes.mbtiles.json b/tests/cartesian/shapes.mbtiles.json new file mode 100644 index 000000000..d9edd034f --- /dev/null +++ b/tests/cartesian/shapes.mbtiles.json @@ -0,0 +1,202 @@ +{ "type": "FeatureCollection", "properties": { +"bounds": "-10.000000,-10.000000,50.000000,25.000000", +"cartesian": "true", +"cartesian_extent": "-50,-50,50,50", +"center": "6.250000,6.250000,3", +"description": "cartesian-shapes", +"format": "pbf", +"json": "{\"vector_layers\": [ { \"id\": \"shapes\", \"description\": \"\", \"minzoom\": 0, \"maxzoom\": 3, \"fields\": {\"name\": \"String\"} } ],\"tilestats\": {\"layerCount\": 1,\"layers\": [{\"layer\": \"shapes\",\"count\": 2,\"geometry\": \"LineString\",\"attributeCount\": 1,\"attributes\": [{\"attribute\": \"name\",\"count\": 2,\"type\": \"string\",\"values\": [\"box\",\"line\"]}]}]}}", +"maxzoom": "3", +"minzoom": "0", +"name": "cartesian-shapes", +"type": "overlay", +"version": "2" +}, "features": [ +{ "type": "FeatureCollection", "properties": { "zoom": 0, "x": 0, "y": 0 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "shapes", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "name": "box" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 9.985352, 10.009766 ], [ 9.985352, -9.985352 ], [ -10.009766, -9.985352 ], [ -10.009766, 10.009766 ], [ 9.985352, 10.009766 ] ] ] } } +, +{ "type": "Feature", "properties": { "name": "line" }, "geometry": { "type": "LineString", "coordinates": [ [ 0.000000, 0.000000 ], [ 25.000000, 25.000000 ], [ 50.000000, 0.000000 ] ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 1, "x": 0, "y": 1 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "shapes", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "name": "box" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 0.976562, 0.976562 ], [ 0.976562, -9.997559 ], [ -10.009766, -9.997559 ], [ -10.009766, 0.976562 ], [ 0.976562, 0.976562 ] ] ] } } +, +{ "type": "Feature", "properties": { "name": "line" }, "geometry": { "type": "LineString", "coordinates": [ [ 0.000000, 0.000000 ], [ 0.976562, 0.976562 ] ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 1, "x": 0, "y": 0 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "shapes", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "name": "box" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 0.976562, 10.009766 ], [ 0.976562, -0.976562 ], [ -10.009766, -0.976562 ], [ -10.009766, 10.009766 ], [ 0.976562, 10.009766 ] ] ] } } +, +{ "type": "Feature", "properties": { "name": "line" }, "geometry": { "type": "LineString", "coordinates": [ [ 0.000000, 0.000000 ], [ 0.976562, 0.976562 ] ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 1, "x": 1, "y": 1 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "shapes", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "name": "box" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 9.997559, 0.976562 ], [ 9.997559, -9.997559 ], [ -0.976562, -9.997559 ], [ -0.976562, 0.976562 ], [ 9.997559, 0.976562 ] ] ] } } +, +{ "type": "Feature", "properties": { "name": "line" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 0.000000, 0.000000 ], [ 0.976562, 0.976562 ] ], [ [ 49.023438, 0.976562 ], [ 50.000000, 0.000000 ] ] ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 1, "x": 1, "y": 0 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "shapes", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "name": "box" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 9.997559, 10.009766 ], [ 9.997559, -0.976562 ], [ -0.976562, -0.976562 ], [ -0.976562, 10.009766 ], [ 9.997559, 10.009766 ] ] ] } } +, +{ "type": "Feature", "properties": { "name": "line" }, "geometry": { "type": "LineString", "coordinates": [ [ 0.000000, 0.000000 ], [ 25.000000, 25.000000 ], [ 50.000000, 0.000000 ] ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 2, "x": 1, "y": 2 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "shapes", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "name": "box" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 0.488281, 0.488281 ], [ 0.488281, -9.997559 ], [ -10.003662, -9.997559 ], [ -10.003662, 0.488281 ], [ 0.488281, 0.488281 ] ] ] } } +, +{ "type": "Feature", "properties": { "name": "line" }, "geometry": { "type": "LineString", "coordinates": [ [ 0.000000, 0.000000 ], [ 0.488281, 0.488281 ] ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 2, "x": 1, "y": 1 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "shapes", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "name": "box" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 0.488281, 10.003662 ], [ 0.488281, -0.488281 ], [ -10.003662, -0.488281 ], [ -10.003662, 10.003662 ], [ 0.488281, 10.003662 ] ] ] } } +, +{ "type": "Feature", "properties": { "name": "line" }, "geometry": { "type": "LineString", "coordinates": [ [ 0.000000, 0.000000 ], [ 0.488281, 0.488281 ] ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 2, "x": 2, "y": 2 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "shapes", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "name": "box" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 9.997559, 0.488281 ], [ 9.997559, -9.997559 ], [ -0.488281, -9.997559 ], [ -0.488281, 0.488281 ], [ 9.997559, 0.488281 ] ] ] } } +, +{ "type": "Feature", "properties": { "name": "line" }, "geometry": { "type": "LineString", "coordinates": [ [ 0.000000, 0.000000 ], [ 0.488281, 0.488281 ] ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 2, "x": 2, "y": 1 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "shapes", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "name": "box" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 9.997559, 10.003662 ], [ 9.997559, -0.488281 ], [ -0.488281, -0.488281 ], [ -0.488281, 10.003662 ], [ 9.997559, 10.003662 ] ] ] } } +, +{ "type": "Feature", "properties": { "name": "line" }, "geometry": { "type": "LineString", "coordinates": [ [ 0.000000, 0.000000 ], [ 25.000000, 25.000000 ], [ 25.488281, 24.511719 ] ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 2, "x": 2, "y": 0 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "shapes", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "name": "line" }, "geometry": { "type": "LineString", "coordinates": [ [ 24.511719, 24.511719 ], [ 25.000000, 25.000000 ], [ 25.488281, 24.511719 ] ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 2, "x": 3, "y": 2 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "shapes", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "name": "line" }, "geometry": { "type": "LineString", "coordinates": [ [ 49.511719, 0.488281 ], [ 50.000000, 0.000000 ] ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 2, "x": 3, "y": 1 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "shapes", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "name": "line" }, "geometry": { "type": "LineString", "coordinates": [ [ 24.511719, 24.511719 ], [ 25.000000, 25.000000 ], [ 50.000000, 0.000000 ] ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 2, "x": 3, "y": 0 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "shapes", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "name": "line" }, "geometry": { "type": "LineString", "coordinates": [ [ 24.511719, 24.511719 ], [ 25.000000, 25.000000 ], [ 25.488281, 24.511719 ] ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 3, "x": 3, "y": 4 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "shapes", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "name": "box" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 0.244141, 0.244141 ], [ 0.244141, -9.997559 ], [ -10.000610, -9.997559 ], [ -10.000610, 0.244141 ], [ 0.244141, 0.244141 ] ] ] } } +, +{ "type": "Feature", "properties": { "name": "line" }, "geometry": { "type": "LineString", "coordinates": [ [ 0.000000, 0.000000 ], [ 0.244141, 0.244141 ] ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 3, "x": 3, "y": 3 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "shapes", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "name": "box" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 0.244141, 10.000610 ], [ 0.244141, -0.244141 ], [ -10.000610, -0.244141 ], [ -10.000610, 10.000610 ], [ 0.244141, 10.000610 ] ] ] } } +, +{ "type": "Feature", "properties": { "name": "line" }, "geometry": { "type": "LineString", "coordinates": [ [ 0.000000, 0.000000 ], [ 0.244141, 0.244141 ] ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 3, "x": 4, "y": 4 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "shapes", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "name": "box" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 9.997559, 0.244141 ], [ 9.997559, -9.997559 ], [ -0.244141, -9.997559 ], [ -0.244141, 0.244141 ], [ 9.997559, 0.244141 ] ] ] } } +, +{ "type": "Feature", "properties": { "name": "line" }, "geometry": { "type": "LineString", "coordinates": [ [ 0.000000, 0.000000 ], [ 0.244141, 0.244141 ] ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 3, "x": 4, "y": 3 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "shapes", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "name": "box" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 9.997559, 10.000610 ], [ 9.997559, -0.244141 ], [ -0.244141, -0.244141 ], [ -0.244141, 10.000610 ], [ 9.997559, 10.000610 ] ] ] } } +, +{ "type": "Feature", "properties": { "name": "line" }, "geometry": { "type": "LineString", "coordinates": [ [ 0.000000, 0.000000 ], [ 12.500000, 12.500000 ], [ 12.744141, 12.744141 ] ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 3, "x": 4, "y": 2 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "shapes", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "name": "line" }, "geometry": { "type": "LineString", "coordinates": [ [ 12.255859, 12.255859 ], [ 12.500000, 12.500000 ], [ 12.744141, 12.744141 ] ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 3, "x": 5, "y": 3 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "shapes", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "name": "line" }, "geometry": { "type": "LineString", "coordinates": [ [ 12.255859, 12.255859 ], [ 12.500000, 12.500000 ], [ 12.744141, 12.744141 ] ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 3, "x": 5, "y": 2 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "shapes", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "name": "line" }, "geometry": { "type": "LineString", "coordinates": [ [ 12.255859, 12.255859 ], [ 12.500000, 12.500000 ], [ 25.000000, 25.000000 ], [ 25.244141, 24.755859 ] ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 3, "x": 5, "y": 1 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "shapes", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "name": "line" }, "geometry": { "type": "LineString", "coordinates": [ [ 24.755859, 24.755859 ], [ 25.000000, 25.000000 ], [ 25.244141, 24.755859 ] ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 3, "x": 6, "y": 3 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "shapes", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "name": "line" }, "geometry": { "type": "LineString", "coordinates": [ [ 37.255859, 12.744141 ], [ 37.500000, 12.500000 ], [ 37.744141, 12.255859 ] ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 3, "x": 6, "y": 2 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "shapes", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "name": "line" }, "geometry": { "type": "LineString", "coordinates": [ [ 24.755859, 24.755859 ], [ 25.000000, 25.000000 ], [ 37.500000, 12.500000 ], [ 37.744141, 12.255859 ] ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 3, "x": 6, "y": 1 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "shapes", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "name": "line" }, "geometry": { "type": "LineString", "coordinates": [ [ 24.755859, 24.755859 ], [ 25.000000, 25.000000 ], [ 25.244141, 24.755859 ] ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 3, "x": 7, "y": 4 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "shapes", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "name": "line" }, "geometry": { "type": "LineString", "coordinates": [ [ 49.755859, 0.244141 ], [ 50.000000, 0.000000 ] ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 3, "x": 7, "y": 3 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "shapes", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "name": "line" }, "geometry": { "type": "LineString", "coordinates": [ [ 37.255859, 12.744141 ], [ 37.500000, 12.500000 ], [ 50.000000, 0.000000 ] ] } } +] } +] } +, +{ "type": "FeatureCollection", "properties": { "zoom": 3, "x": 7, "y": 2 }, "crs": { "type": "name", "properties": { "name": "cartesian" } }, "features": [ +{ "type": "FeatureCollection", "properties": { "layer": "shapes", "version": 2, "extent": 4096 }, "features": [ +{ "type": "Feature", "properties": { "name": "line" }, "geometry": { "type": "LineString", "coordinates": [ [ 37.255859, 12.744141 ], [ 37.500000, 12.500000 ], [ 37.744141, 12.255859 ] ] } } +] } +] } +] } diff --git a/tile-join.cpp b/tile-join.cpp index 25e63ef7f..893f1f9e4 100644 --- a/tile-join.cpp +++ b/tile-join.cpp @@ -647,8 +647,8 @@ void decode(struct reader *readers, std::map &layer } double lat1, lon1, lat2, lon2; - tile2lonlat(r->x, r->y, r->zoom, &lon1, &lat1); - tile2lonlat(r->x + 1, r->y + 1, r->zoom, &lon2, &lat2); + projection->unproject(r->x, r->y, r->zoom, &lon1, &lat1); + projection->unproject(r->x + 1, r->y + 1, r->zoom, &lon2, &lat2); minlat = min(lat2, minlat); minlon = min(lon1, minlon); maxlat = max(lat1, maxlat); @@ -840,6 +840,8 @@ void decode(struct reader *readers, std::map &layer sqlite3_finalize(r->stmt); } + set_cartesian_from_metadata(db); + // Closes either real db or temp mirror of metadata.json if (sqlite3_close(db) != SQLITE_OK) { fprintf(stderr, "Could not close database: %s\n", sqlite3_errmsg(db)); @@ -909,6 +911,9 @@ int main(int argc, char **argv) { {"empty-csv-columns-are-null", no_argument, &pe, 1}, {"no-tile-stats", no_argument, &pg, 1}, + {"cartesian", no_argument, 0, '~'}, + {"cartesian-extent", required_argument, 0, '~'}, + {0, 0, 0, 0}, }; @@ -929,7 +934,8 @@ int main(int argc, char **argv) { std::string commandline = format_commandline(argc, argv); - while ((i = getopt_long(argc, argv, getopt_str.c_str(), long_options, NULL)) != -1) { + int option_index = 0; + while ((i = getopt_long(argc, argv, getopt_str.c_str(), long_options, &option_index)) != -1) { switch (i) { case 0: break; @@ -1035,6 +1041,35 @@ int main(int argc, char **argv) { quiet = true; break; + case '~': { + const char *opt = long_options[option_index].name; + if (strcmp(opt, "cartesian") == 0) { + cartesian_mode = true; + projection = get_projection("cartesian"); + } else if (strcmp(opt, "cartesian-extent") == 0) { + if (sscanf(optarg, "%lf,%lf,%lf,%lf", + &cartesian_extent[0], &cartesian_extent[1], + &cartesian_extent[2], &cartesian_extent[3]) != 4) { + fprintf(stderr, "%s: Can't parse Cartesian extent: %s\n", argv[0], optarg); + exit(EXIT_FAILURE); + } + double width = cartesian_extent[2] - cartesian_extent[0]; + double height = cartesian_extent[3] - cartesian_extent[1]; + if (width <= 0 || height <= 0) { + fprintf(stderr, "%s: --cartesian-extent must have positive width and height (got %g,%g,%g,%g)\n", + argv[0], cartesian_extent[0], cartesian_extent[1], cartesian_extent[2], cartesian_extent[3]); + exit(EXIT_FAILURE); + } + cartesian_extent_set = true; + cartesian_mode = true; + projection = get_projection("cartesian"); + } else { + fprintf(stderr, "%s: Unrecognized option: --%s\n", argv[0], opt); + usage(argv); + } + break; + } + default: usage(argv); } diff --git a/tile.cpp b/tile.cpp index cf419825a..d058a32e0 100644 --- a/tile.cpp +++ b/tile.cpp @@ -1226,7 +1226,7 @@ struct write_tile_args { bool clip_to_tile(serial_feature &sf, int z, long long buffer) { int quick = quick_check(sf.bbox, z, buffer); - if (z == 0) { + if (z == 0 && !cartesian_mode) { if (sf.bbox[0] <= (1LL << 32) * buffer / 256 || sf.bbox[2] >= (1LL << 32) - ((1LL << 32) * buffer / 256)) { // If the geometry extends off the edge of the world, concatenate on another copy // shifted by 360 degrees, and then make sure both copies get clipped down to size.