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.