From 1b675466b0fc91f0b49917cb06b99f7387250d55 Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Thu, 15 Jun 2023 02:34:27 -0400 Subject: [PATCH] Implement sprite support Serve image assets sprites for MapLibre rendering, given a directory with images. ### API Per [MapLibre sprites API](https://maplibre.org/maplibre-style-spec/sprite/), we need to support the following: * `/sprite/.json` metadata about the sprite file - all coming from a single directory * `/sprite/.png` all images combined into a single PNG * `/sprite/@2x.json` same but for high DPI devices * `/sprite/@2x.png` Multiple sprite_id values can be combined into one sprite with the same pattern as for tile joining: `/sprite/,,...,[.json|.png|@2x.json|@2x.png]`. No ID renaming is done, so identical names will override one another. ### Configuration [Config file](https://maplibre.org/martin/config-file.html) and possibly CLI should have a simple option to serve sprites. The configuration may look similar to how mbtiles and pmtiles are configured: ```yaml # Publish sprite images sprites: paths: # scan this whole dir, matching all image files, and publishing it as "my_images" sprite source - /path/to/my_images sources: # named source matching source name to a directory my_sprites: /path/to/some_dir ``` Implement #705 --- Cargo.lock | 811 +++++++++++++++++- Cargo.toml | 2 + README.md | 1 + justfile | 1 + martin/Cargo.toml | 3 + martin/src/config.rs | 28 +- martin/src/file_config.rs | 41 +- martin/src/lib.rs | 7 +- martin/src/pg/config.rs | 3 +- martin/src/pg/configurator.rs | 3 +- martin/src/source.rs | 91 +- martin/src/sprites/mod.rs | 214 +++++ martin/src/srv/server.rs | 57 +- martin/src/utils/error.rs | 4 + martin/src/utils/id_resolver.rs | 93 ++ martin/src/utils/mod.rs | 2 + tests/config.yaml | 5 + tests/expected/configured/spr_cmp.json | 30 + tests/expected/configured/spr_cmp.png | Bin 0 -> 823 bytes tests/expected/configured/spr_cmp.png.txt | 1 + tests/expected/configured/spr_cmp_2x.json | 30 + tests/expected/configured/spr_cmp_2x.png | Bin 0 -> 1546 bytes tests/expected/configured/spr_cmp_2x.png.txt | 1 + tests/expected/configured/spr_mysrc.json | 9 + tests/expected/configured/spr_mysrc.png | Bin 0 -> 265 bytes tests/expected/configured/spr_mysrc.png.txt | 1 + tests/expected/configured/spr_mysrc_2x.json | 9 + tests/expected/configured/spr_mysrc_2x.png | Bin 0 -> 399 bytes .../expected/configured/spr_mysrc_2x.png.txt | 1 + tests/expected/configured/spr_src1.json | 23 + tests/expected/configured/spr_src1.png | Bin 0 -> 801 bytes tests/expected/configured/spr_src1.png.txt | 1 + tests/expected/configured/spr_src1_2x.json | 23 + tests/expected/configured/spr_src1_2x.png | Bin 0 -> 1490 bytes tests/expected/configured/spr_src1_2x.png.txt | 1 + tests/expected/given_config.yaml | 1 + tests/fixtures/sprites/expected/all_1.json | 30 + tests/fixtures/sprites/expected/all_1.png | Bin 0 -> 823 bytes tests/fixtures/sprites/expected/all_2.json | 30 + tests/fixtures/sprites/expected/all_2.png | Bin 0 -> 1546 bytes tests/fixtures/sprites/expected/src1_1.json | 23 + tests/fixtures/sprites/expected/src1_1.png | Bin 0 -> 801 bytes tests/fixtures/sprites/expected/src1_2.json | 23 + tests/fixtures/sprites/expected/src1_2.png | Bin 0 -> 1490 bytes tests/fixtures/sprites/expected/src2_1.json | 9 + tests/fixtures/sprites/expected/src2_1.png | Bin 0 -> 265 bytes tests/fixtures/sprites/expected/src2_2.json | 9 + tests/fixtures/sprites/expected/src2_2.png | Bin 0 -> 399 bytes .../fixtures/sprites/src1/another_bicycle.svg | 3 + tests/fixtures/sprites/src1/bear.svg | 12 + tests/fixtures/sprites/src1/sub/circle.svg | 3 + tests/fixtures/sprites/src2/bicycle.svg | 12 + tests/test.sh | 13 + 53 files changed, 1533 insertions(+), 131 deletions(-) create mode 100644 martin/src/sprites/mod.rs create mode 100644 martin/src/utils/id_resolver.rs create mode 100644 tests/expected/configured/spr_cmp.json create mode 100644 tests/expected/configured/spr_cmp.png create mode 100644 tests/expected/configured/spr_cmp.png.txt create mode 100644 tests/expected/configured/spr_cmp_2x.json create mode 100644 tests/expected/configured/spr_cmp_2x.png create mode 100644 tests/expected/configured/spr_cmp_2x.png.txt create mode 100644 tests/expected/configured/spr_mysrc.json create mode 100644 tests/expected/configured/spr_mysrc.png create mode 100644 tests/expected/configured/spr_mysrc.png.txt create mode 100644 tests/expected/configured/spr_mysrc_2x.json create mode 100644 tests/expected/configured/spr_mysrc_2x.png create mode 100644 tests/expected/configured/spr_mysrc_2x.png.txt create mode 100644 tests/expected/configured/spr_src1.json create mode 100644 tests/expected/configured/spr_src1.png create mode 100644 tests/expected/configured/spr_src1.png.txt create mode 100644 tests/expected/configured/spr_src1_2x.json create mode 100644 tests/expected/configured/spr_src1_2x.png create mode 100644 tests/expected/configured/spr_src1_2x.png.txt create mode 100644 tests/fixtures/sprites/expected/all_1.json create mode 100644 tests/fixtures/sprites/expected/all_1.png create mode 100644 tests/fixtures/sprites/expected/all_2.json create mode 100644 tests/fixtures/sprites/expected/all_2.png create mode 100644 tests/fixtures/sprites/expected/src1_1.json create mode 100644 tests/fixtures/sprites/expected/src1_1.png create mode 100644 tests/fixtures/sprites/expected/src1_2.json create mode 100644 tests/fixtures/sprites/expected/src1_2.png create mode 100644 tests/fixtures/sprites/expected/src2_1.json create mode 100644 tests/fixtures/sprites/expected/src2_1.png create mode 100644 tests/fixtures/sprites/expected/src2_2.json create mode 100644 tests/fixtures/sprites/expected/src2_2.png create mode 100644 tests/fixtures/sprites/src1/another_bicycle.svg create mode 100644 tests/fixtures/sprites/src1/bear.svg create mode 100644 tests/fixtures/sprites/src1/sub/circle.svg create mode 100644 tests/fixtures/sprites/src2/bicycle.svg diff --git a/Cargo.lock b/Cargo.lock index 7f9eadc8d..c03f30a6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -263,6 +263,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + [[package]] name = "aho-corasick" version = "1.0.2" @@ -354,6 +363,33 @@ version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" +[[package]] +name = "arrayref" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + +[[package]] +name = "arrayvec" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8868f09ff8cea88b079da74ae569d9b8c62a23c68c746240b704ee6f7525c89c" + +[[package]] +name = "assert_fs" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f070617a68e5c2ed5d06ee8dd620ee18fb72b99f6c094bed34cf8ab07c875b48" +dependencies = [ + "anstyle", + "doc-comment", + "globwalk", + "predicates", + "predicates-core", + "predicates-tree", + "tempfile", +] + [[package]] name = "async-compression" version = "0.3.15" @@ -401,6 +437,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -419,6 +466,18 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -449,12 +508,28 @@ dependencies = [ "alloc-stdlib", ] +[[package]] +name = "bstr" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a246e68bb43f6cd9db24bea052a53e40405417c5fb372e3d1a8a7f770a564ef5" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "bumpalo" version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +[[package]] +name = "bytemuck" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" + [[package]] name = "byteorder" version = "1.4.3" @@ -530,6 +605,21 @@ dependencies = [ "half", ] +[[package]] +name = "clap" +version = "3.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +dependencies = [ + "atty", + "bitflags", + "clap_lex 0.2.4", + "indexmap", + "strsim", + "termcolor", + "textwrap", +] + [[package]] name = "clap" version = "4.3.4" @@ -550,7 +640,7 @@ dependencies = [ "anstream", "anstyle", "bitflags", - "clap_lex", + "clap_lex 0.5.0", "strsim", ] @@ -566,12 +656,27 @@ dependencies = [ "syn 2.0.18", ] +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + [[package]] name = "clap_lex" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + [[package]] name = "colorchoice" version = "1.0.0" @@ -653,7 +758,7 @@ dependencies = [ "anes", "cast", "ciborium", - "clap", + "clap 4.3.4", "criterion-plot", "futures", "is-terminal", @@ -735,6 +840,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crunch" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc013e70da3bfe5b552de26a1f34ecf67d61ea811251d2bf75c1324a1ecb425" + [[package]] name = "crypto-common" version = "0.1.6" @@ -755,6 +866,12 @@ dependencies = [ "syn 2.0.18", ] +[[package]] +name = "data-url" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d7439c3735f405729d52c3fbbe4de140eaf938a1fe47d227c27f8254d4302a5" + [[package]] name = "deadpool" version = "0.9.5" @@ -802,6 +919,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + [[package]] name = "digest" version = "0.10.7" @@ -813,6 +936,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "dotenvy" version = "0.15.7" @@ -900,6 +1029,12 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "exitcode" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de853764b47027c2e862a995c34978ffa63c1501f2e15f987ba11bd4f9bba193" + [[package]] name = "fallible-iterator" version = "0.2.0" @@ -915,6 +1050,27 @@ dependencies = [ "instant", ] +[[package]] +name = "fdeflate" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "filetime" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.2.16", + "windows-sys 0.48.0", +] + [[package]] name = "flate2" version = "1.0.26" @@ -925,6 +1081,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" + [[package]] name = "flume" version = "0.10.14" @@ -960,6 +1122,28 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "fontconfig-parser" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ab2e12762761366dcb876ab8b6e0cfa4797ddcd890575919f008b5ba655672a" +dependencies = [ + "roxmltree", +] + +[[package]] +name = "fontdb" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "237ff9f0813bbfc9de836016472e0c9ae7802f174a51594607e5f4ff334cb2f5" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2", + "slotmap", + "ttf-parser", +] + [[package]] name = "foreign-types" version = "0.3.2" @@ -996,6 +1180,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.28" @@ -1117,6 +1307,46 @@ dependencies = [ "wasi", ] +[[package]] +name = "gif" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "globset" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc" +dependencies = [ + "aho-corasick 0.7.20", + "bstr", + "fnv", + "log", + "regex", +] + +[[package]] +name = "globwalk" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" +dependencies = [ + "bitflags", + "ignore", + "walkdir", +] + [[package]] name = "h2" version = "0.3.19" @@ -1176,6 +1406,15 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "hermit-abi" version = "0.2.6" @@ -1251,6 +1490,43 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "ignore" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492" +dependencies = [ + "globset", + "lazy_static", + "log", + "memchr", + "regex", + "same-file", + "thread_local", + "walkdir", + "winapi-util", +] + +[[package]] +name = "image" +version = "0.24.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527909aa81e20ac3a44803521443a765550f09b5130c2c2fa1ea59c2f8f50a3a" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "num-rational", + "num-traits", + "png", +] + +[[package]] +name = "imagesize" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b72ad49b554c1728b1e83254a1b1565aea4161e28dabbfa171fc15fe62299caf" + [[package]] name = "indexmap" version = "1.9.3" @@ -1259,6 +1535,7 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", + "rayon", ] [[package]] @@ -1329,6 +1606,12 @@ dependencies = [ "libc", ] +[[package]] +name = "jpeg-decoder" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" + [[package]] name = "js-sys" version = "0.3.64" @@ -1338,6 +1621,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "kurbo" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd85a5776cd9500c2e2059c8c76c3b01528566b7fcbaf8098b55a33fc298849b" +dependencies = [ + "arrayvec", +] + [[package]] name = "language-tags" version = "0.3.2" @@ -1356,6 +1648,24 @@ version = "0.2.146" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" +[[package]] +name = "libdeflate-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6784b6b84b67d71b4307963d456a9c7c29f9b47c658f533e598de369e34277" +dependencies = [ + "cc", +] + +[[package]] +name = "libdeflater" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8e285aa6a046fd338b2592c16bee148b2b00789138ed6b7bb56bb13d585050d" +dependencies = [ + "libdeflate-sys", +] + [[package]] name = "libsqlite3-sys" version = "0.24.2" @@ -1425,7 +1735,7 @@ dependencies = [ "async-trait", "brotli", "cargo-husky", - "clap", + "clap 4.3.4", "criterion", "ctor", "deadpool-postgres", @@ -1449,9 +1759,11 @@ dependencies = [ "serde", "serde_json", "serde_yaml", + "spreet", "subst", "thiserror", "tilejson", + "tokio", ] [[package]] @@ -1460,7 +1772,7 @@ version = "0.2.1" dependencies = [ "actix-rt", "anyhow", - "clap", + "clap 4.3.4", "futures", "log", "martin-tile-utils", @@ -1490,6 +1802,15 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "memmap2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +dependencies = [ + "libc", +] + [[package]] name = "memmapix" version = "0.6.3" @@ -1527,6 +1848,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", + "simd-adler32", ] [[package]] @@ -1541,6 +1863,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "multimap" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70db9248a93dc36a36d9a47898caa007a32755c7ad140ec64eeeb50d5a730631" +dependencies = [ + "serde", +] + [[package]] name = "native-tls" version = "0.2.11" @@ -1569,6 +1900,27 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.15" @@ -1654,6 +2006,36 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "os_str_bytes" +version = "6.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" + +[[package]] +name = "oxipng" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630638e107fb436644c300e781d3f17e1b04656138ba0d40564be4be3b06db32" +dependencies = [ + "bitvec", + "clap 3.2.25", + "crossbeam-channel", + "filetime", + "image", + "indexmap", + "itertools", + "libdeflater", + "log", + "rayon", + "rgb", + "rustc-hash", + "rustc_version", + "stderrlog", + "wild", + "zopfli", +] + [[package]] name = "parking_lot" version = "0.11.2" @@ -1758,6 +2140,12 @@ dependencies = [ "siphasher", ] +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + [[package]] name = "pin-project" version = "1.1.0" @@ -1843,6 +2231,19 @@ dependencies = [ "varint-rs", ] +[[package]] +name = "png" +version = "0.17.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59871cc5b6cce7eaccca5a802b4173377a1c2ba90654246789a8fa2334426d11" +dependencies = [ + "bitflags", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "postgis" version = "0.9.0" @@ -1920,6 +2321,34 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "predicates" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09963355b9f467184c04017ced4a2ba2d75cbcb4e7462690d388233253d4b1a9" +dependencies = [ + "anstyle", + "difflib", + "itertools", + "predicates-core", +] + +[[package]] +name = "predicates-core" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" + +[[package]] +name = "predicates-tree" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" +dependencies = [ + "predicates-core", + "termtree", +] + [[package]] name = "proc-macro2" version = "1.0.60" @@ -1938,6 +2367,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -1990,6 +2425,12 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "rctree" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b42e27ef78c35d3998403c1d26f3efd9e135d3e5121b0a4845cc5cc27547f4f" + [[package]] name = "redox_syscall" version = "0.2.16" @@ -2014,7 +2455,7 @@ version = "1.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" dependencies = [ - "aho-corasick", + "aho-corasick 1.0.2", "memchr", "regex-syntax 0.7.2", ] @@ -2031,12 +2472,67 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" +[[package]] +name = "resvg" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c95f7305220347e1d798efaf9c95ea0cad67dd62cd85b012e218e5266fbb6114" +dependencies = [ + "gif", + "jpeg-decoder", + "log", + "pico-args", + "png", + "rgb", + "svgfilters", + "svgtypes", + "tiny-skia", + "usvg", +] + [[package]] name = "retain_mut" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4389f1d5789befaf6029ebd9f7dac4af7f7e3d61b69d4f30e2ac02b57e7712b0" +[[package]] +name = "rgb" +version = "0.8.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20ec2d3e3fc7a92ced357df9cebd5a10b6fb2aa1ee797bf7e9ce2f17dffc8f59" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "rosvgtree" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cdb7996003c5cc8a8c2585b4ab6b422da64ad86a9c99cfa7ba320e15e8739f3" +dependencies = [ + "log", + "roxmltree", + "simplecss", + "siphasher", + "svgtypes", +] + +[[package]] +name = "roxmltree" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8f595a457b6b8c6cda66a48503e92ee8d19342f905948f29c383200ec9eb1d8" +dependencies = [ + "xmlparser", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.4.0" @@ -2074,6 +2570,22 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "rustybuzz" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162bdf42e261bee271b3957691018634488084ef577dddeb6420a9684cab2a6a" +dependencies = [ + "bitflags", + "bytemuck", + "smallvec", + "ttf-parser", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-general-category", + "unicode-script", +] + [[package]] name = "ryu" version = "1.0.13" @@ -2242,6 +2754,21 @@ dependencies = [ "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "238abfbb77c1915110ad968465608b68e869e0772622c9656714e73e5a1a522f" + +[[package]] +name = "simplecss" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a11be7c62927d9427e9f40f3444d5499d868648e2edbc4e2116de69e7ec0e89d" +dependencies = [ + "log", +] + [[package]] name = "siphasher" version = "0.3.10" @@ -2257,6 +2784,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "slotmap" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342" +dependencies = [ + "version_check", +] + [[package]] name = "smallvec" version = "1.10.0" @@ -2292,6 +2828,23 @@ dependencies = [ "lock_api", ] +[[package]] +name = "spreet" +version = "0.8.0-dev" +dependencies = [ + "assert_fs", + "clap 4.3.4", + "crunch", + "exitcode", + "multimap", + "oxipng", + "png", + "rayon", + "resvg", + "serde", + "serde_json", +] + [[package]] name = "sqlformat" version = "0.2.1" @@ -2391,6 +2944,27 @@ dependencies = [ "tokio-native-tls", ] +[[package]] +name = "stderrlog" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69a26bbf6de627d389164afa9783739b56746c6c72c4ed16539f4ff54170327b" +dependencies = [ + "atty", + "log", + "termcolor", + "thread_local", +] + +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +dependencies = [ + "float-cmp", +] + [[package]] name = "stringprep" version = "0.1.2" @@ -2448,6 +3022,26 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +[[package]] +name = "svgfilters" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "639abcebc15fdc2df179f37d6f5463d660c1c79cd552c12343a4600827a04bce" +dependencies = [ + "float-cmp", + "rgb", +] + +[[package]] +name = "svgtypes" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed4b0611e7f3277f68c0fa18e385d9e2d26923691379690039548f867cef02a7" +dependencies = [ + "kurbo", + "siphasher", +] + [[package]] name = "syn" version = "1.0.109" @@ -2470,6 +3064,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tempfile" version = "3.6.0" @@ -2486,13 +3086,25 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.2.0" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" dependencies = [ "winapi-util", ] +[[package]] +name = "termtree" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" + +[[package]] +name = "textwrap" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" + [[package]] name = "thiserror" version = "1.0.40" @@ -2513,6 +3125,16 @@ dependencies = [ "syn 2.0.18", ] +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "tilejson" version = "0.3.2" @@ -2551,6 +3173,31 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny-skia" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8493a203431061e901613751931f047d1971337153f96d0e5e363d6dbf6a67" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "png", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adbfb5d3f3dd57a0e11d12f4f13d4ebbbc1b5c15b7ab0a156d030b21da5f677c" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -2698,6 +3345,18 @@ dependencies = [ "once_cell", ] +[[package]] +name = "ttf-parser" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0609f771ad9c6155384897e1df4d948e692667cc0588548b68eb44d052b27633" + +[[package]] +name = "typed-arena" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" + [[package]] name = "typenum" version = "1.16.0" @@ -2710,6 +3369,24 @@ version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +[[package]] +name = "unicode-bidi-mirroring" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56d12260fb92d52f9008be7e4bca09f584780eb2266dc8fecc6a192bec561694" + +[[package]] +name = "unicode-ccc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2520efa644f8268dce4dcd3050eaa7fc044fca03961e9998ac7e2e92b77cf1" + +[[package]] +name = "unicode-general-category" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2281c8c1d221438e373249e065ca4989c4c36952c211ff21a0ee91c44a3869e7" + [[package]] name = "unicode-ident" version = "1.0.9" @@ -2725,12 +3402,24 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-script" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d817255e1bed6dfd4ca47258685d14d2bdcfbc64fdc9e3819bd5848057b8ecc" + [[package]] name = "unicode-segmentation" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +[[package]] +name = "unicode-vo" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" + [[package]] name = "unicode-width" version = "0.1.10" @@ -2760,6 +3449,66 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "usvg" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a6cab2bc32b5a4310a06c7d3c6b51b5c7897b1f7c7d2bf73bf052f5754950f" +dependencies = [ + "base64", + "log", + "pico-args", + "usvg-parser", + "usvg-text-layout", + "usvg-tree", + "xmlwriter", +] + +[[package]] +name = "usvg-parser" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2352a2c05655a7e4d3dca76cf65764efce35527472668bae5c6fc876b4c996d" +dependencies = [ + "data-url", + "flate2", + "imagesize", + "kurbo", + "log", + "rosvgtree", + "strict-num", + "svgtypes", + "usvg-tree", +] + +[[package]] +name = "usvg-text-layout" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "392baafaaa861ff8c9863546f92a60c51380fc49aa185a6840fb2af564c73530" +dependencies = [ + "fontdb", + "kurbo", + "log", + "rustybuzz", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "usvg-tree", +] + +[[package]] +name = "usvg-tree" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9cb92fe40e0ffb45fd01349187e276a695f6c676a016d72ba09510009594829" +dependencies = [ + "kurbo", + "rctree", + "strict-num", + "svgtypes", +] + [[package]] name = "utf8parse" version = "0.2.1" @@ -2870,6 +3619,21 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "weezl" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" + +[[package]] +name = "wild" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b116685a6be0c52f5a103334cbff26db643826c7b3735fc0a3ba9871310a74" +dependencies = [ + "glob", +] + [[package]] name = "winapi" version = "0.3.9" @@ -3024,6 +3788,39 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "xmlparser" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d25c75bf9ea12c4040a97f829154768bbbce366287e2dc044af160cd79a13fd" + +[[package]] +name = "xmlwriter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" + +[[package]] +name = "zopfli" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e0650ae6a051326d798eb099b632f1afb0d323d25ee4ec82ffb0779512084d5" +dependencies = [ + "crc32fast", + "log", + "simd-adler32", + "typed-arena", +] + [[package]] name = "zstd" version = "0.11.2+zstd.1.5.2" diff --git a/Cargo.toml b/Cargo.toml index aab866b6d..d7e8d7630 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,11 +36,13 @@ postgis = "0.9" postgres = { version = "0.19", features = ["with-time-0_3", "with-uuid-1", "with-serde_json-1"] } postgres-openssl = "0.5" postgres-protocol = "0.6" +rayon = "1.7" regex = "1" semver = "1" serde = { version = "1", features = ["derive"] } serde_json = "1" serde_yaml = "0.9" +spreet = { version = "0.8.0-dev", path = "../spreet" } sqlx = { version = "0.6", features = ["offline", "sqlite", "runtime-actix-native-tls"] } subst = { version = "0.2", features = ["yaml"] } thiserror = "1" diff --git a/README.md b/README.md index c015b1de9..9eec46c95 100755 --- a/README.md +++ b/README.md @@ -93,6 +93,7 @@ Martin data is available via the HTTP `GET` endpoints: | `/{sourceID}/{z}/{x}/{y}` | Map Tiles | | `/{source1},...,{sourceN}` | Composite Source TileJSON | | `/{source1},...,{sourceN}/{z}/{x}/{y}` | Composite Source Tiles | +| `/sprite/{spriteID}[@2x].{json,png}` | Sprites (low and high DPI, index/png) | | `/health` | Martin server health check: returns 200 `OK` | ## Documentation diff --git a/justfile b/justfile index 24c6cd395..e5dd00118 100644 --- a/justfile +++ b/justfile @@ -106,6 +106,7 @@ test-int: clean-test # Run integration tests and save its output as the new expected output bless: start clean-test + cargo test --features bless-tests tests/test.sh rm -rf tests/expected mv tests/output tests/expected diff --git a/martin/Cargo.toml b/martin/Cargo.toml index 69ea5e640..bce076731 100644 --- a/martin/Cargo.toml +++ b/martin/Cargo.toml @@ -25,6 +25,7 @@ path = "src/bin/main.rs" default = [] ssl = ["openssl", "postgres-openssl"] vendored-openssl = ["ssl", "openssl/vendored"] +bless-tests = [] [dependencies] actix-cors.workspace = true @@ -53,9 +54,11 @@ semver.workspace = true serde.workspace = true serde_json = { workspace = true, features = ["preserve_order"] } serde_yaml.workspace = true +spreet.workspace = true subst.workspace = true thiserror.workspace = true tilejson.workspace = true +tokio = { workspace = true, features = ["io-std"] } # Optional dependencies for openssl support openssl = { workspace = true, optional = true } diff --git a/martin/src/config.rs b/martin/src/config.rs index 314814156..783bb912b 100644 --- a/martin/src/config.rs +++ b/martin/src/config.rs @@ -14,10 +14,14 @@ use crate::file_config::{resolve_files, FileConfigEnum}; use crate::mbtiles::MbtSource; use crate::pg::PgConfig; use crate::pmtiles::PmtSource; -use crate::source::{IdResolver, Sources}; +use crate::source::Sources; +use crate::sprites::{resolve_sprites, SpriteSources}; use crate::srv::SrvConfig; use crate::utils::{OneOrMany, Result}; use crate::Error::{ConfigLoadError, ConfigParseError, NoSources}; +use crate::IdResolver; + +pub type SourceTypes = (Sources, SpriteSources); #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] pub struct Config { @@ -33,6 +37,9 @@ pub struct Config { #[serde(skip_serializing_if = "Option::is_none")] pub mbtiles: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub sprites: Option, + #[serde(flatten)] pub unrecognized: HashMap, } @@ -73,7 +80,7 @@ impl Config { } } - pub async fn resolve(&mut self, idr: IdResolver) -> Result { + pub async fn resolve(&mut self, idr: IdResolver) -> Result { let create_pmt_src = &mut PmtSource::new_box; let create_mbt_src = &mut MbtSource::new_box; @@ -93,13 +100,16 @@ impl Config { sources.push(Box::pin(val)); } - Ok(try_join_all(sources) - .await? - .into_iter() - .fold(HashMap::new(), |mut acc, hashmap| { - acc.extend(hashmap); - acc - })) + Ok(( + try_join_all(sources) + .await? + .into_iter() + .fold(HashMap::new(), |mut acc, hashmap| { + acc.extend(hashmap); + acc + }), + resolve_sprites(&mut self.sprites)?, + )) } } diff --git a/martin/src/file_config.rs b/martin/src/file_config.rs index 4d546d1d2..64cea32ce 100644 --- a/martin/src/file_config.rs +++ b/martin/src/file_config.rs @@ -40,6 +40,22 @@ pub enum FileConfigEnum { Config(FileConfig), } +impl FileConfigEnum { + pub fn extract_file_config(&mut self) -> FileConfig { + match self { + FileConfigEnum::Path(path) => FileConfig { + paths: Some(One(mem::take(path))), + ..FileConfig::default() + }, + FileConfigEnum::Paths(paths) => FileConfig { + paths: Some(Many(mem::take(paths))), + ..Default::default() + }, + FileConfigEnum::Config(cfg) => mem::take(cfg), + } + } +} + #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] pub struct FileConfig { /// A list of file paths @@ -69,12 +85,12 @@ pub enum FileConfigSrc { } impl FileConfigSrc { - #[must_use] - pub fn path(&self) -> &PathBuf { - match self { + pub fn path(&self) -> Result { + let path = match self { Self::Path(p) => p, Self::Obj(o) => &o.path, - } + }; + path.canonicalize().map_err(|e| IoError(e, path.clone())) } } @@ -125,17 +141,7 @@ async fn resolve_int( where Fut: Future, FileError>>, { - let cfg = match config { - FileConfigEnum::Path(path) => FileConfig { - paths: Some(One(mem::take(path))), - ..FileConfig::default() - }, - FileConfigEnum::Paths(paths) => FileConfig { - paths: Some(Many(mem::take(paths))), - ..Default::default() - }, - FileConfigEnum::Config(cfg) => mem::take(cfg), - }; + let cfg = config.extract_file_config(); let mut results = Sources::new(); let mut configs = HashMap::new(); @@ -144,8 +150,7 @@ where if let Some(sources) = cfg.sources { for (id, source) in sources { - let path = source.path(); - let can = path.canonicalize().map_err(|e| IoError(e, path.clone()))?; + let can = source.path()?; if !can.is_file() { // todo: maybe warn instead? return Err(InvalidSourceFilePath(id.to_string(), can)); @@ -173,7 +178,7 @@ where directories.push(path.clone()); path.read_dir() .map_err(|e| IoError(e, path.clone()))? - .filter_map(std::result::Result::ok) + .filter_map(Result::ok) .filter(|f| { f.path().extension().filter(|e| *e == extension).is_some() && f.path().is_file() diff --git a/martin/src/lib.rs b/martin/src/lib.rs index 6284e7160..9c560b75b 100644 --- a/martin/src/lib.rs +++ b/martin/src/lib.rs @@ -14,6 +14,7 @@ pub mod mbtiles; pub mod pg; pub mod pmtiles; mod source; +pub mod sprites; pub mod srv; mod utils; @@ -26,8 +27,10 @@ mod test_utils; #[cfg(test)] pub use crate::args::Env; pub use crate::config::{read_config, Config}; -pub use crate::source::{IdResolver, Source, Sources, Xyz}; -pub use crate::utils::{decode_brotli, decode_gzip, BoolOrObject, Error, OneOrMany, Result}; +pub use crate::source::{Source, Sources, Xyz}; +pub use crate::utils::{ + decode_brotli, decode_gzip, BoolOrObject, Error, IdResolver, OneOrMany, Result, +}; // Ensure README.md contains valid code #[cfg(doctest)] diff --git a/martin/src/pg/config.rs b/martin/src/pg/config.rs index dc4472c2c..545c87119 100644 --- a/martin/src/pg/config.rs +++ b/martin/src/pg/config.rs @@ -7,8 +7,9 @@ use crate::pg::config_function::FuncInfoSources; use crate::pg::config_table::TableInfoSources; use crate::pg::configurator::PgBuilder; use crate::pg::utils::Result; -use crate::source::{IdResolver, Sources}; +use crate::source::Sources; use crate::utils::{sorted_opt_map, BoolOrObject, OneOrMany}; +use crate::IdResolver; pub trait PgInfo { fn format_id(&self) -> String; diff --git a/martin/src/pg/configurator.rs b/martin/src/pg/configurator.rs index 3f16ee9d5..479368e60 100755 --- a/martin/src/pg/configurator.rs +++ b/martin/src/pg/configurator.rs @@ -1,6 +1,7 @@ use std::cmp::Ordering; use std::collections::{HashMap, HashSet}; +use crate::IdResolver; use futures::future::join_all; use itertools::Itertools; use log::{debug, error, info, warn}; @@ -14,7 +15,7 @@ use crate::pg::pool::PgPool; use crate::pg::table_source::{calc_srid, get_table_sources, merge_table_info, table_to_query}; use crate::pg::utils::PgError::InvalidTableExtent; use crate::pg::utils::Result; -use crate::source::{IdResolver, Sources}; +use crate::source::Sources; use crate::utils::{find_info, normalize_key, BoolOrObject, InfoMap, OneOrMany}; pub type SqlFuncInfoMapMap = InfoMap>; diff --git a/martin/src/source.rs b/martin/src/source.rs index e813f2c6b..78fa3fb21 100644 --- a/martin/src/source.rs +++ b/martin/src/source.rs @@ -1,10 +1,7 @@ -use std::collections::hash_map::Entry; -use std::collections::{HashMap, HashSet}; -use std::fmt::{Debug, Display, Formatter, Write}; -use std::sync::{Arc, Mutex}; - use async_trait::async_trait; use martin_tile_utils::TileInfo; +use std::collections::HashMap; +use std::fmt::{Debug, Display, Formatter}; use tilejson::TileJSON; use crate::utils::Result; @@ -51,94 +48,10 @@ impl Clone for Box { } } -#[derive(Debug, Default, Clone)] -pub struct IdResolver { - /// name -> unique name - names: Arc>>, - /// reserved names - reserved: HashSet<&'static str>, -} - -impl IdResolver { - #[must_use] - pub fn new(reserved_keywords: &[&'static str]) -> Self { - Self { - names: Arc::new(Mutex::new(HashMap::new())), - reserved: reserved_keywords.iter().copied().collect(), - } - } - - /// If source name already exists in the self.names structure, - /// try appending it with ".1", ".2", etc. until the name is unique. - /// Only alphanumeric characters plus dashes/dots/underscores are allowed. - #[must_use] - pub fn resolve(&self, name: &str, unique_name: String) -> String { - // Ensure name has no prohibited characters like spaces, commas, slashes, or non-unicode etc. - // Underscores, dashes, and dots are OK. All other characters will be replaced with dashes. - let mut name = name.replace( - |c: char| !c.is_ascii_alphanumeric() && c != '_' && c != '.' && c != '-', - "-", - ); - - let mut names = self.names.lock().expect("IdResolver panicked"); - if !self.reserved.contains(name.as_str()) { - match names.entry(name) { - Entry::Vacant(e) => { - let id = e.key().clone(); - e.insert(unique_name); - return id; - } - Entry::Occupied(e) => { - name = e.key().clone(); - if e.get() == &unique_name { - return name; - } - } - } - } - // name already exists, try it with ".1", ".2", etc. until the value matches - // assume that reserved keywords never end in a "dot number", so don't check - let mut index: i32 = 1; - let mut new_name = String::new(); - loop { - new_name.clear(); - write!(&mut new_name, "{name}.{index}").unwrap(); - index = index.checked_add(1).unwrap(); - match names.entry(new_name.clone()) { - Entry::Vacant(e) => { - e.insert(unique_name); - return new_name; - } - Entry::Occupied(e) => { - if e.get() == &unique_name { - return new_name; - } - } - } - } - } -} - #[cfg(test)] mod tests { use super::*; - #[test] - fn id_resolve() { - let r = IdResolver::default(); - assert_eq!(r.resolve("a", "a".to_string()), "a"); - assert_eq!(r.resolve("a", "a".to_string()), "a"); - assert_eq!(r.resolve("a", "b".to_string()), "a.1"); - assert_eq!(r.resolve("a", "b".to_string()), "a.1"); - assert_eq!(r.resolve("b", "a".to_string()), "b"); - assert_eq!(r.resolve("b", "a".to_string()), "b"); - assert_eq!(r.resolve("a.1", "a".to_string()), "a.1.1"); - assert_eq!(r.resolve("a.1", "b".to_string()), "a.1"); - - assert_eq!(r.resolve("a b", "a b".to_string()), "a-b"); - assert_eq!(r.resolve("a b", "ab2".to_string()), "a-b.1"); - } - #[test] fn xyz_format() { let xyz = Xyz { z: 1, x: 2, y: 3 }; diff --git a/martin/src/sprites/mod.rs b/martin/src/sprites/mod.rs new file mode 100644 index 000000000..7ca1834a8 --- /dev/null +++ b/martin/src/sprites/mod.rs @@ -0,0 +1,214 @@ +use crate::file_config::{FileConfigEnum, FileError}; +use futures::future::try_join_all; +use log::{info, warn}; +use spreet::fs::get_svg_input_paths; +use spreet::resvg::tiny_skia::Pixmap; +use spreet::resvg::usvg::{Error as ResvgError, Options, Tree, TreeParsing}; +use spreet::sprite::{generate_pixmap_from_svg, sprite_name, Spritesheet, SpritesheetBuilder}; +use std::collections::hash_map::Entry; +use std::collections::HashMap; +use std::fmt::Debug; +use std::path::PathBuf; +use tokio::io::AsyncReadExt; + +#[derive(thiserror::Error, Debug)] +pub enum SpriteError { + #[error("IO error {0}: {}", .1.display())] + IoError(std::io::Error, PathBuf), + + #[error("Sprite path is not a file: {}", .0.display())] + InvalidFilePath(PathBuf), + + #[error("Sprite {0} uses bad file {}", .1.display())] + InvalidSpriteFilePath(String, PathBuf), + + #[error("No sprite files found in {}", .0.display())] + NoSpriteFilesFound(PathBuf), + + #[error("Sprite {} could not be loaded", .0.display())] + UnableToReadSprite(PathBuf), + + #[error("{0} in file {}", .1.display())] + SpriteProcessingError(spreet::error::Error, PathBuf), + + #[error("{0} in file {}", .1.display())] + SpriteParsingError(ResvgError, PathBuf), + + #[error("Unable to generate spritesheet")] + UnableToGenerateSpritesheet, +} + +pub fn resolve_sprites(cfg: &mut Option) -> Result { + let Some(cfg) = cfg else { + return Ok(SpriteSources::default()); + }; + let cfg = cfg.extract_file_config(); + let mut results = SpriteSources::new(); + + if let Some(sources) = cfg.sources { + for (id, source) in sources { + add_source(id, source.path()?, &mut results); + } + }; + + if let Some(paths) = cfg.paths { + for path in paths { + let Some(name) = path.file_name() else { + warn!("Ignoring sprite source with no name from {}", path.display()); + continue; + }; + add_source(name.to_string_lossy().to_string(), path, &mut results); + } + } + + Ok(results) +} + +fn add_source(id: String, path: PathBuf, results: &mut HashMap) { + let disp_path = path.display(); + if path.is_file() { + warn!("Ignoring non-directory sprite source {id} from {disp_path}"); + } else { + match results.entry(id) { + Entry::Occupied(v) => { + warn!("Ignoring duplicate sprite source {} from {disp_path} because it was already configured for {}", + v.key(), v.get().path.display()); + } + Entry::Vacant(v) => { + info!("Configured sprite source {} from {disp_path}", v.key()); + v.insert(SpriteSource { path }); + } + } + }; +} + +pub type SpriteSources = HashMap; + +#[derive(Clone, Debug)] +pub struct SpriteSource { + path: PathBuf, +} + +async fn parse_sprite( + name: String, + path: PathBuf, + pixel_ratio: u8, +) -> Result<(String, Pixmap), SpriteError> { + let on_err = |e| SpriteError::IoError(e, path.clone()); + + let mut file = tokio::fs::File::open(&path).await.map_err(on_err)?; + + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer).await.map_err(on_err)?; + + let tree = Tree::from_data(&buffer, &Options::default()) + .map_err(|e| SpriteError::SpriteParsingError(e, path.clone()))?; + + let pixmap = generate_pixmap_from_svg(&tree, pixel_ratio) + .ok_or_else(|| SpriteError::UnableToReadSprite(path.clone()))?; + + Ok((name, pixmap)) +} + +pub async fn get_spritesheet( + sources: impl Iterator, + pixel_ratio: u8, +) -> Result { + // Asynchronously load all SVG files from the given sources + let sprites = try_join_all(sources.flat_map(|source| { + get_svg_input_paths(&source.path, true) + .into_iter() + .map(|svg_path| { + let name = sprite_name(&svg_path, &source.path); + parse_sprite(name, svg_path, pixel_ratio) + }) + .collect::>() + })) + .await?; + + let mut builder = SpritesheetBuilder::new(); + builder + .sprites(sprites.into_iter().collect()) + .pixel_ratio(pixel_ratio); + + // TODO: decide if this is needed and/or configurable + // builder.make_unique(); + + builder + .generate() + .ok_or(SpriteError::UnableToGenerateSpritesheet) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::file_config::FileConfig; + use crate::OneOrMany::Many; + use std::path::PathBuf; + + #[actix_rt::test] + async fn test_sprites() { + let config = FileConfig { + paths: Some(Many(vec![ + PathBuf::from("../tests/fixtures/sprites/src1"), + PathBuf::from("../tests/fixtures/sprites/src2"), + ])), + ..FileConfig::default() + }; + + let sprites = resolve_sprites(&mut Some(FileConfigEnum::Config(config))).unwrap(); + assert_eq!(sprites.len(), 2); + + test_src(sprites.values(), 1, "all_1").await; + test_src(sprites.values(), 2, "all_2").await; + + test_src(sprites.get("src1").into_iter(), 1, "src1_1").await; + test_src(sprites.get("src1").into_iter(), 2, "src1_2").await; + + test_src(sprites.get("src2").into_iter(), 1, "src2_1").await; + test_src(sprites.get("src2").into_iter(), 2, "src2_2").await; + } + + async fn test_src( + sources: impl Iterator, + pixel_ratio: u8, + filename: &str, + ) { + let path = PathBuf::from(format!("../tests/fixtures/sprites/expected/{filename}")); + + let sprites = get_spritesheet(sources, pixel_ratio).await.unwrap(); + let mut json = serde_json::to_string_pretty(sprites.get_index()).unwrap(); + json.push('\n'); + let png = sprites.encode_png().unwrap(); + + #[cfg(feature = "bless-tests")] + { + use std::io::Write as _; + let mut file = std::fs::File::create(path.with_extension("json")).unwrap(); + file.write_all(json.as_bytes()).unwrap(); + + let mut file = std::fs::File::create(path.with_extension("png")).unwrap(); + file.write_all(&png).unwrap(); + } + + #[cfg(not(feature = "bless-tests"))] + { + let expected = std::fs::read_to_string(path.with_extension("json")) + .expect("Unable to open expected JSON file, make sure to bless tests with\n cargo test --features bless-tests\n"); + + assert_eq!( + serde_json::from_str::(&json).unwrap(), + serde_json::from_str::(&expected).unwrap(), + "Make sure to run bless if needed:\n cargo test --features bless-tests\n\n{json}", + ); + + let expected = std::fs::read(path.with_extension("png")) + .expect("Unable to open expected PNG file, make sure to bless tests with\n cargo test --features bless-tests\n"); + + assert_eq!( + png, expected, + "Make sure to run bless if needed:\n cargo test --features bless-tests\n\n{json}", + ); + } + } +} diff --git a/martin/src/srv/server.rs b/martin/src/srv/server.rs index e992e7ccc..c4234f5ef 100755 --- a/martin/src/srv/server.rs +++ b/martin/src/srv/server.rs @@ -2,12 +2,14 @@ use std::cmp::Ordering; use std::string::ToString; use std::time::Duration; +use crate::config::SourceTypes; use actix_cors::Cors; use actix_http::ContentEncoding; use actix_web::dev::Server; use actix_web::error::ErrorBadRequest; use actix_web::http::header::{ - AcceptEncoding, Encoding as HeaderEnc, HeaderValue, Preference, CACHE_CONTROL, CONTENT_ENCODING, + AcceptEncoding, ContentType, Encoding as HeaderEnc, HeaderValue, Preference, CACHE_CONTROL, + CONTENT_ENCODING, }; use actix_web::http::Uri; use actix_web::middleware::TrailingSlash; @@ -21,9 +23,11 @@ use itertools::Itertools; use log::{debug, error}; use martin_tile_utils::{Encoding, Format, TileInfo}; use serde::{Deserialize, Serialize}; +use spreet::sprite::Spritesheet; use tilejson::{tilejson, TileJSON}; use crate::source::{Source, Sources, UrlQuery, Xyz}; +use crate::sprites::{get_spritesheet, SpriteSource, SpriteSources}; use crate::srv::config::{SrvConfig, KEEP_ALIVE_DEFAULT, LISTEN_ADDRESSES_DEFAULT}; use crate::utils::{decode_brotli, decode_gzip, encode_brotli, encode_gzip}; use crate::Error::BindingError; @@ -43,9 +47,16 @@ static SUPPORTED_ENCODINGS: &[HeaderEnc] = &[ pub struct AppState { pub sources: Sources, + pub sprites: SpriteSources, } impl AppState { + fn get_sprite_source(&self, id: &str) -> Result<&SpriteSource> { + self.sprites + .get(id) + .ok_or_else(|| error::ErrorNotFound(format!("Sprite {id} does not exist"))) + } + fn get_source(&self, id: &str) -> Result<&dyn Source> { Ok(self .sources @@ -181,6 +192,41 @@ async fn get_catalog(state: Data) -> impl Responder { HttpResponse::Ok().json(info) } +#[route("/sprite/{source_ids}.png", method = "GET", method = "HEAD")] +async fn get_sprite_png( + path: Path, + state: Data, +) -> Result { + let ss = get_sprite_int(&path.source_ids, &state).await?; + Ok(HttpResponse::Ok() + .content_type(ContentType::png()) + .body(ss.encode_png().map_err(map_internal_error)?)) +} + +#[route("/sprite/{source_ids}.json", method = "GET", method = "HEAD")] +async fn get_sprite_json( + path: Path, + state: Data, +) -> Result { + let ss = get_sprite_int(&path.source_ids, &state).await?; + Ok(HttpResponse::Ok().json(ss.get_index())) +} + +async fn get_sprite_int(ids: &str, state: &AppState) -> Result { + let (ids, dpi) = if let Some(ids) = ids.strip_suffix("@2x") { + (ids, 2) + } else { + (ids, 1) + }; + let sprite_ids = ids + .split(',') + .map(|id| state.get_sprite_source(id)) + .collect::>>()?; + get_spritesheet(sprite_ids.into_iter(), dpi) + .await + .map_err(map_internal_error) +} + #[route( "/{source_ids}", method = "GET", @@ -458,11 +504,13 @@ pub fn router(cfg: &mut web::ServiceConfig) { .service(get_index) .service(get_catalog) .service(git_source_info) - .service(get_tile); + .service(get_tile) + .service(get_sprite_json) + .service(get_sprite_png); } /// Create a new initialized Actix `App` instance together with the listening address. -pub fn new_server(config: SrvConfig, sources: Sources) -> crate::Result<(Server, String)> { +pub fn new_server(config: SrvConfig, sources: SourceTypes) -> crate::Result<(Server, String)> { let keep_alive = Duration::from_secs(config.keep_alive.unwrap_or(KEEP_ALIVE_DEFAULT)); let worker_processes = config.worker_processes.unwrap_or_else(num_cpus::get); let listen_addresses = config @@ -471,7 +519,8 @@ pub fn new_server(config: SrvConfig, sources: Sources) -> crate::Result<(Server, let server = HttpServer::new(move || { let state = AppState { - sources: sources.clone(), + sources: sources.0.clone(), + sprites: sources.1.clone(), }; let cors_middleware = Cors::default() diff --git a/martin/src/utils/error.rs b/martin/src/utils/error.rs index b9a945d1f..44829c34e 100644 --- a/martin/src/utils/error.rs +++ b/martin/src/utils/error.rs @@ -3,6 +3,7 @@ use std::path::PathBuf; use crate::file_config::FileError; use crate::pg::PgError; +use crate::sprites::SpriteError; pub type Result = std::result::Result; @@ -34,4 +35,7 @@ pub enum Error { #[error("{0}")] FileError(#[from] FileError), + + #[error("{0}")] + SpriteError(#[from] SpriteError), } diff --git a/martin/src/utils/id_resolver.rs b/martin/src/utils/id_resolver.rs new file mode 100644 index 000000000..ff1fdd2a4 --- /dev/null +++ b/martin/src/utils/id_resolver.rs @@ -0,0 +1,93 @@ +use std::collections::hash_map::Entry; +use std::collections::{HashMap, HashSet}; +use std::fmt::Write; +use std::sync::{Arc, Mutex}; + +#[derive(Debug, Default, Clone)] +pub struct IdResolver { + /// name -> unique name + names: Arc>>, + /// reserved names + reserved: HashSet<&'static str>, +} + +impl IdResolver { + #[must_use] + pub fn new(reserved_keywords: &[&'static str]) -> Self { + Self { + names: Arc::new(Mutex::new(HashMap::new())), + reserved: reserved_keywords.iter().copied().collect(), + } + } + + /// If source name already exists in the self.names structure, + /// try appending it with ".1", ".2", etc. until the name is unique. + /// Only alphanumeric characters plus dashes/dots/underscores are allowed. + #[must_use] + pub fn resolve(&self, name: &str, unique_name: String) -> String { + // Ensure name has no prohibited characters like spaces, commas, slashes, or non-unicode etc. + // Underscores, dashes, and dots are OK. All other characters will be replaced with dashes. + let mut name = name.replace( + |c: char| !c.is_ascii_alphanumeric() && c != '_' && c != '.' && c != '-', + "-", + ); + + let mut names = self.names.lock().expect("IdResolver panicked"); + if !self.reserved.contains(name.as_str()) { + match names.entry(name) { + Entry::Vacant(e) => { + let id = e.key().clone(); + e.insert(unique_name); + return id; + } + Entry::Occupied(e) => { + name = e.key().clone(); + if e.get() == &unique_name { + return name; + } + } + } + } + // name already exists, try it with ".1", ".2", etc. until the value matches + // assume that reserved keywords never end in a "dot number", so don't check + let mut index: i32 = 1; + let mut new_name = String::new(); + loop { + new_name.clear(); + write!(&mut new_name, "{name}.{index}").unwrap(); + index = index.checked_add(1).unwrap(); + match names.entry(new_name.clone()) { + Entry::Vacant(e) => { + e.insert(unique_name); + return new_name; + } + Entry::Occupied(e) => { + if e.get() == &unique_name { + return new_name; + } + } + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn id_resolve() { + let r = IdResolver::default(); + assert_eq!(r.resolve("a", "a".to_string()), "a"); + assert_eq!(r.resolve("a", "a".to_string()), "a"); + assert_eq!(r.resolve("a", "b".to_string()), "a.1"); + assert_eq!(r.resolve("a", "b".to_string()), "a.1"); + assert_eq!(r.resolve("b", "a".to_string()), "b"); + assert_eq!(r.resolve("b", "a".to_string()), "b"); + assert_eq!(r.resolve("a.1", "a".to_string()), "a.1.1"); + assert_eq!(r.resolve("a.1", "b".to_string()), "a.1"); + + assert_eq!(r.resolve("a b", "a b".to_string()), "a-b"); + assert_eq!(r.resolve("a b", "ab2".to_string()), "a-b.1"); + } +} diff --git a/martin/src/utils/mod.rs b/martin/src/utils/mod.rs index 15ebc4811..43aff165d 100644 --- a/martin/src/utils/mod.rs +++ b/martin/src/utils/mod.rs @@ -1,7 +1,9 @@ mod error; +mod id_resolver; mod one_or_many; mod utilities; pub use error::*; +pub use id_resolver::IdResolver; pub use one_or_many::OneOrMany; pub use utilities::*; diff --git a/tests/config.yaml b/tests/config.yaml index 0019cf255..15e39c09f 100644 --- a/tests/config.yaml +++ b/tests/config.yaml @@ -155,3 +155,8 @@ postgres: pmtiles: sources: pmt: tests/fixtures/files/stamen_toner__raster_CC-BY+ODbL_z3.pmtiles + +sprites: + paths: tests/fixtures/sprites/src1 + sources: + mysrc: tests/fixtures/sprites/src2 diff --git a/tests/expected/configured/spr_cmp.json b/tests/expected/configured/spr_cmp.json new file mode 100644 index 000000000..7828c798e --- /dev/null +++ b/tests/expected/configured/spr_cmp.json @@ -0,0 +1,30 @@ +{ + "another_bicycle": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 20, + "y": 16 + }, + "bear": { + "height": 16, + "pixelRatio": 1, + "width": 16, + "x": 20, + "y": 0 + }, + "bicycle": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 35, + "y": 16 + }, + "sub/circle": { + "height": 20, + "pixelRatio": 1, + "width": 20, + "x": 0, + "y": 0 + } +} diff --git a/tests/expected/configured/spr_cmp.png b/tests/expected/configured/spr_cmp.png new file mode 100644 index 0000000000000000000000000000000000000000..04f3a647030b96826f0489b423f7da28e894ebcc GIT binary patch literal 823 zcmeAS@N?(olHy`uVBq!ia0vp^MnEjj!3-oPUCzs9U|^ID@Ck7Ra_1)){0CAD3`eJA zPmj=;8mbLsPYczU9HIeaObO8fk`M)Zy3&CR2zyGX=G0)#DIuD0S)h7|6i^(Y`UR~%7Q4b+EREBP-3|W%tysJGGS$t<}^18C1Md=RfD?=dSKogG7 zs5~~UY*S6d^l;s&p;|AGt$)0K*{z*RZf=-%dhN`UvujVzt~oNLWM6L%Q2Um;=*@Le z6RUGq=6f~gC(Tc?oEc@<7ViRdTcf8k(99SUNiQuC6HO&;RYg@%Ex0D! z-D#q4SOs-l7`dBxb5?Ima4d}dz3+eWjztc8b{qZqH2dV8IVWu=zhSxG_Q+B*HE)k_ zJll5d?8GZA*R^&x8|;xl);G@Y;=8MV|E}IGURzhh;T*$ugfYZIU%q?R;^K4K z+t^;N`4ztC;i3DCf)iW1KWxgdisPT`_I#d7(SaTg306Z@Ucn_U2V^?tn5gX2I`8jP zeL0+~#bm96zMUP{+mmsItafYOo|<#7=10J*r*8Lsnof8)v(+woHGi-6*HeF2yNtw(w#N2qDd^v zX8ciYGdtq3ST=88A)mM}Yv_^t7kyK{{P)<=_?(rY>)5nOfeNdfOtu}mG()2@d0NKt zX(3T()15DS=^9F{jp%G#*(z{aOeU1Em!sG-rqWi^pw z_8R7f%CDYEIT3{n%&yDrJX=IRPkDYtz4!lP;R><6N%0kWhQJ7B@O1TaS?83{1OTQ0 BOnU$T literal 0 HcmV?d00001 diff --git a/tests/expected/configured/spr_cmp.png.txt b/tests/expected/configured/spr_cmp.png.txt new file mode 100644 index 000000000..85e2609b4 --- /dev/null +++ b/tests/expected/configured/spr_cmp.png.txt @@ -0,0 +1 @@ +tests/output/configured/spr_cmp.png: PNG image data, 50 x 31, 8-bit colormap, non-interlaced diff --git a/tests/expected/configured/spr_cmp_2x.json b/tests/expected/configured/spr_cmp_2x.json new file mode 100644 index 000000000..b95ec54b2 --- /dev/null +++ b/tests/expected/configured/spr_cmp_2x.json @@ -0,0 +1,30 @@ +{ + "another_bicycle": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 40, + "y": 32 + }, + "bear": { + "height": 32, + "pixelRatio": 2, + "width": 32, + "x": 40, + "y": 0 + }, + "bicycle": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 70, + "y": 32 + }, + "sub/circle": { + "height": 40, + "pixelRatio": 2, + "width": 40, + "x": 0, + "y": 0 + } +} diff --git a/tests/expected/configured/spr_cmp_2x.png b/tests/expected/configured/spr_cmp_2x.png new file mode 100644 index 0000000000000000000000000000000000000000..48d9c7f8b1efcc599f50c9590f0bbeb4dde1ff0a GIT binary patch literal 1546 zcmZWpdpy%?82`Cnvg48@!cNXI6DGOlp4&z^Y=^_j66-|buu@rAr<~(1Hktd7tjQE| zH<1##E8%3*DwJz0#l-o2KIEVCem?K>e4poizTfwK-anoM4>u=>w2CwU01%Y3y(j1c z!EGfe0@@GSZeIaFsNKUA;{b+>{j5SfauY-V&`AloVYic+m~Fre zN7a#hDHueSm-bd!bK)6_7o3WPgF0ZQDL!CZ=LcnnOTlbYs6V*1ak$!897qQ#TdW5p z@4D-!w(yoM3w7Em0QLVbwR4p3Z_JE1z7y^MMwm0@$8|f&r`eZBD>LbV7R3b1VuJMu zGcDiW1Z;f#K{m}7N&6i+`-JtDeTT;`8*eC`tS^|XD;TNH9jnS3VWbW-Qu>N-w9r$Z z<&qv#h#i^4)|7Lt$sr9lPS?cvRmb>NMtR+g4rPR+8DZ$svv!36W_c%#a&U$;UjyQ4 zZz>i6J|ou4G|BBC8KvoswguY^vr@+$*@w~JYi@AJ42iIXDcJs^V5F(61N#}Nsj9C6 z(Nlq_DJ#MDNdF|WOGZip{0hqC(Js*8iJop=fE>6qpin3hiL|h`wzjdcL809}JUqPp z@%XUt@bK8!*tod3w6wJJ^z_`k!os4W;xZPis;Y_2X1BGq_4oG=3=E7l+(IbWK|T5V?i2x5Zowt%_9?U(z*%X=9R>+wmoe@cYIq zWm%7nb&VBnq7BI9FM_khLyQnc7+rXygZsU&E*Cc0`H=+A_H@m2@CTy3`!Bir5n`=*>$k^U%fnBq zHDxtA8kt5MF>(i@U25r6#|x`frHPi!UaWTrbg2*CS#wD5;{{T}1a1`8BU!N<{ikju z^W;;+#i}>d3b$S60V%m9jrE>k0Zu7v-EVkv1v>F#-|0QdQ_;;9qB6`1<)EJZqoc_7 z9$6tRrVp>EB|*MVSc@(bqb;H=Wy(W8%ZiZHga9QKq6B~|>1Yltt78vH#Z{Mwu76j3 z_-|`U>#@0Eg?;C#>#osCpOnH?_h>byiTTMB@tKhzp}>+&gxN&CzWb$6kGCdKFzNYth2@Z<^pTE!%Ri z!qs@0iiU~jUxoFVq7S&ypDaz@-fMxTr(cznP#3hlelxEwXQu5hY4K%kZF-?hXLOmU z@9eWH@jjkB7`fjvq&JN<6ECZ(^Kt!5;<|rv5pPfP88wp>B99zI*NJ$Z+oDr}bNyTF zbwtMH-(DNk4=m##c1~iS6ooG4_^K&o))rNxhVXmO9yrPze@@t)zjz-Im(O&ZRqZ>j z-H$n6um4G%Pwxqz?F*U_LuS~!Hb$fFzT(cZrNfD1bGU(84IJ}Ty9F>GjNS|WNEBG$dihXsK@9Xr?`{nXCi&k!lhk28w4x% zFoSY}PGlx$5UjH4&8x>cQ{8R5V-vs`#PF6_Jm#TF}ydSayemuG)6ju93X>%Kk> zip(KyKTR(9&C|E}ZL-!()&ku6((b=948%XJp9e{)_Y&S&yf+&?Lb|Lg*`?Gv@% yTNr6c-0Cx2*__F^LMJ;XM|oOcA*bH|KOY$%PhttUwBSev$U~m4elF{r5}E)>6iu`M literal 0 HcmV?d00001 diff --git a/tests/expected/configured/spr_mysrc.png.txt b/tests/expected/configured/spr_mysrc.png.txt new file mode 100644 index 000000000..9be03516b --- /dev/null +++ b/tests/expected/configured/spr_mysrc.png.txt @@ -0,0 +1 @@ +tests/output/configured/spr_mysrc.png: PNG image data, 15 x 15, 8-bit colormap, non-interlaced diff --git a/tests/expected/configured/spr_mysrc_2x.json b/tests/expected/configured/spr_mysrc_2x.json new file mode 100644 index 000000000..b7ec269c2 --- /dev/null +++ b/tests/expected/configured/spr_mysrc_2x.json @@ -0,0 +1,9 @@ +{ + "bicycle": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 0, + "y": 0 + } +} diff --git a/tests/expected/configured/spr_mysrc_2x.png b/tests/expected/configured/spr_mysrc_2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c5269e3201467167b7d5b1d8efdd0e539dbccdab GIT binary patch literal 399 zcmeAS@N?(olHy`uVBq!ia0vp^av;pX3?zBp#Z3TG-T^)#u0Wbv;8}uI3D7Kwk|4ie z1_1>FdxwC8g8GL32@BS5*ni;sh5HZQfB60C(l4Nz+nz3tAr-gIP6*69tRTQDZ}DKp zwqtE9=l}XgH3o)Se%U>fKjGV3FYPaOISiil8-peNbF&UqX!Skvd*ID$-J^T-O10L5 z_UN;d(kg_$o;ssedtf>1;`kY=2e%#Aa!AKjZi3k5<6LtO8G5cO`K;UeBIOTXinb+# zzW8g+;2BqMZ#etD@dnq?7d6|K&bxE<&NjEVEUI%gTe~DWM4f@0pBy literal 0 HcmV?d00001 diff --git a/tests/expected/configured/spr_mysrc_2x.png.txt b/tests/expected/configured/spr_mysrc_2x.png.txt new file mode 100644 index 000000000..826bb7405 --- /dev/null +++ b/tests/expected/configured/spr_mysrc_2x.png.txt @@ -0,0 +1 @@ +tests/output/configured/spr_mysrc_2x.png: PNG image data, 30 x 30, 8-bit colormap, non-interlaced diff --git a/tests/expected/configured/spr_src1.json b/tests/expected/configured/spr_src1.json new file mode 100644 index 000000000..578634f50 --- /dev/null +++ b/tests/expected/configured/spr_src1.json @@ -0,0 +1,23 @@ +{ + "another_bicycle": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 20, + "y": 16 + }, + "bear": { + "height": 16, + "pixelRatio": 1, + "width": 16, + "x": 20, + "y": 0 + }, + "sub/circle": { + "height": 20, + "pixelRatio": 1, + "width": 20, + "x": 0, + "y": 0 + } +} diff --git a/tests/expected/configured/spr_src1.png b/tests/expected/configured/spr_src1.png new file mode 100644 index 0000000000000000000000000000000000000000..65ebd4e06aa07f04ea2a1ef34b7d2499f47c4e16 GIT binary patch literal 801 zcmeAS@N?(olHy`uVBq!ia0vp^DnKmH!3-pKB+oy>z`!UQ;1l8s-WK zo*tnwHB=kOo))SxIYa}yt>dEq8>tSs0`gu8L}kPc~^TXviQ!{EXIlL$zKWTmN|fvRgZs+}tqj^xByxXV;#bU2|kg$-dqkp!O|w(VOd{ zCRXRJ%=c=}Pnw@(IWx+zE#3v_wnk57pqViyl3rROCYnmxs*0+jTzov-yxg2XTNa8s zMFJ_Ok|4ie1_1#9c?AOldj|)9|A2t-_=JSa{DOk|`i6#v2@~eeU$9`o`VIT{A2@LO z{P_zPu3o)*{r>$24<0^${`UR*4;hfwuk4DrFQo8W2LjpAchxf6xc&EEd#>J6%B#3;<`Kp(8oA~-Pn@+~YP;Al z+&!yZQtnEz!jiKS9@-@cpG`Iw%ibKw*?Zt7L@5;(EUY+ULzuIaY_4Jh{X^H|MkB8>4Txa<}iUJ$7wDx39V3jV?=5 zww*a|+>g1mUSGaBCGL)YkQu8egUq?0M1?c41ujK5ax?^&D;+MbS^wbHLmTI3ro2pt zzQ-;JnCRi$w02I*(<-g@V9iNPfeGErtp*krF;jQC>v%A9B>6-yk#ks}CwW;eaEFrQ cXZgQOUKQ(hDVP}^14bo-r>mdKI;Vst0A|WPIRF3v literal 0 HcmV?d00001 diff --git a/tests/expected/configured/spr_src1.png.txt b/tests/expected/configured/spr_src1.png.txt new file mode 100644 index 000000000..c4191fec8 --- /dev/null +++ b/tests/expected/configured/spr_src1.png.txt @@ -0,0 +1 @@ +tests/output/configured/spr_src1.png: PNG image data, 36 x 31, 8-bit colormap, non-interlaced diff --git a/tests/expected/configured/spr_src1_2x.json b/tests/expected/configured/spr_src1_2x.json new file mode 100644 index 000000000..7ac8efa9c --- /dev/null +++ b/tests/expected/configured/spr_src1_2x.json @@ -0,0 +1,23 @@ +{ + "another_bicycle": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 40, + "y": 32 + }, + "bear": { + "height": 32, + "pixelRatio": 2, + "width": 32, + "x": 40, + "y": 0 + }, + "sub/circle": { + "height": 40, + "pixelRatio": 2, + "width": 40, + "x": 0, + "y": 0 + } +} diff --git a/tests/expected/configured/spr_src1_2x.png b/tests/expected/configured/spr_src1_2x.png new file mode 100644 index 0000000000000000000000000000000000000000..81bd0a5e393c8219c04bb05cf634bbd2f4b6f4ef GIT binary patch literal 1490 zcmeAS@N?(olHy`uVBq!ia0vp^9zblz!3-q#d#qi_z`%GYz$e5N$bEl){rm)j|3DT4 z!_g_((<3w>?5UyJ5XSzVtZAW8!O0;|#_9sk?KM$TLbRp@YeJOm=}MmxtT8oI185FV zWOrK<)Et-=xZWutP%Tqodbc*jY^{$0Y6oh8=-N~hzP%+8!iJE`a@`>cfaYTw1(%u| zZ??L~7tVzFd-Gt%JY zvGq^(FMqs$*{z*R?(LX&Z~MF}n`htLFzw39zRN3n&n;@*yRh%%?3yD}N)Ash+1H!1 zuP1wFYx0)5=*@Le>&k;CR_CrP@LHMgwItJNev;*!IMZ1%#xtV~OOnHN`m}?83cepUO_>@z`(%9-rnB9!NJqZKOi6=JU$^Izo4L? zzP`Spp`pLOf5L=^xf*6d6TJ)@S6t1y(X>@+s?;j6Embbdul-(H_3!??8zFq0FIRcA{vc3quDf*&x_4)V2>9wCrtDbAzQgKT^%C~v3=Nkj( zv&UZb{t~!l6TE-pt&B@P(aV&|tminKHr(~f^ZM)R4TtCbD)ryxrN1yWAgxKO*+9?C zHg#rPP12JenQw1SZ12CgX}QV$*20Ju3r2ZUo2QfWGnaqgzw?nMZ`1o80l}q9Lru?5 z7b);<4fB1`A`rOrXX(wz<$TvtGHTTpE$)4t92XUx7v$dibnDXNU+ua%s*bno1$MlD zc}ZZ2i>u2Dl`nz&-4x^MIK2E^q}o5Jwa#dnBr@r&2eSy*sT~4k%Cd_Bm?vl*I;7K} zEb@w>!M$~=ih<{;XZQC9o_>4#iS?58XHFGpe=#Oi@vFzP*#B4O|9M}o zJ+H`h?#xT-YrZ(Xiv4kmdqH^hg0r05#TVGjnijUk{U|!*#J5lV{_VS!+4?<8?jFv! z`Q}*t{HDLA!dT%x@ zi*W2n$y@jK=P6Zd|Dcv*Jq5Lge`YHBK5zf+q^{}G@+RdcYsN}FXU%n16Mn5eqqocO zs3e2rGwv7%bEB-y^ONiBj=p)Y&mvj%*&~VK|17IFa@EY+X(G0J+R9CVPowN7teZ7e zdh_8JkHdvmWdr<9@_FlBYMn9rU5Mpnt|G7X{Apjy(yv(F`dq(c`%fnMFV{8a9uY}T zTz@3fbo#Bd>C3OIk6o}pI8*PS+~#vHbDyn>++B08M%+9^Z~2$!Z%f|<%)D%HDTZ&& z+tf-Xw~3n@x7sd0CYg~OQ{0qYt@rszT|)iDo4jk13$xc;y>q}yO+7kIFMaNTUyRn} ZjHjXkKUs9?IRLXYgQu&X%Q~loCID;Qo!0;W literal 0 HcmV?d00001 diff --git a/tests/expected/configured/spr_src1_2x.png.txt b/tests/expected/configured/spr_src1_2x.png.txt new file mode 100644 index 000000000..309919452 --- /dev/null +++ b/tests/expected/configured/spr_src1_2x.png.txt @@ -0,0 +1 @@ +tests/output/configured/spr_src1_2x.png: PNG image data, 72 x 62, 8-bit colormap, non-interlaced diff --git a/tests/expected/given_config.yaml b/tests/expected/given_config.yaml index 30b3f053e..89ceb0d16 100644 --- a/tests/expected/given_config.yaml +++ b/tests/expected/given_config.yaml @@ -117,3 +117,4 @@ postgres: pmtiles: sources: pmt: tests/fixtures/files/stamen_toner__raster_CC-BY+ODbL_z3.pmtiles +sprites: {} diff --git a/tests/fixtures/sprites/expected/all_1.json b/tests/fixtures/sprites/expected/all_1.json new file mode 100644 index 000000000..7828c798e --- /dev/null +++ b/tests/fixtures/sprites/expected/all_1.json @@ -0,0 +1,30 @@ +{ + "another_bicycle": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 20, + "y": 16 + }, + "bear": { + "height": 16, + "pixelRatio": 1, + "width": 16, + "x": 20, + "y": 0 + }, + "bicycle": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 35, + "y": 16 + }, + "sub/circle": { + "height": 20, + "pixelRatio": 1, + "width": 20, + "x": 0, + "y": 0 + } +} diff --git a/tests/fixtures/sprites/expected/all_1.png b/tests/fixtures/sprites/expected/all_1.png new file mode 100644 index 0000000000000000000000000000000000000000..04f3a647030b96826f0489b423f7da28e894ebcc GIT binary patch literal 823 zcmeAS@N?(olHy`uVBq!ia0vp^MnEjj!3-oPUCzs9U|^ID@Ck7Ra_1)){0CAD3`eJA zPmj=;8mbLsPYczU9HIeaObO8fk`M)Zy3&CR2zyGX=G0)#DIuD0S)h7|6i^(Y`UR~%7Q4b+EREBP-3|W%tysJGGS$t<}^18C1Md=RfD?=dSKogG7 zs5~~UY*S6d^l;s&p;|AGt$)0K*{z*RZf=-%dhN`UvujVzt~oNLWM6L%Q2Um;=*@Le z6RUGq=6f~gC(Tc?oEc@<7ViRdTcf8k(99SUNiQuC6HO&;RYg@%Ex0D! z-D#q4SOs-l7`dBxb5?Ima4d}dz3+eWjztc8b{qZqH2dV8IVWu=zhSxG_Q+B*HE)k_ zJll5d?8GZA*R^&x8|;xl);G@Y;=8MV|E}IGURzhh;T*$ugfYZIU%q?R;^K4K z+t^;N`4ztC;i3DCf)iW1KWxgdisPT`_I#d7(SaTg306Z@Ucn_U2V^?tn5gX2I`8jP zeL0+~#bm96zMUP{+mmsItafYOo|<#7=10J*r*8Lsnof8)v(+woHGi-6*HeF2yNtw(w#N2qDd^v zX8ciYGdtq3ST=88A)mM}Yv_^t7kyK{{P)<=_?(rY>)5nOfeNdfOtu}mG()2@d0NKt zX(3T()15DS=^9F{jp%G#*(z{aOeU1Em!sG-rqWi^pw z_8R7f%CDYEIT3{n%&yDrJX=IRPkDYtz4!lP;R><6N%0kWhQJ7B@O1TaS?83{1OTQ0 BOnU$T literal 0 HcmV?d00001 diff --git a/tests/fixtures/sprites/expected/all_2.json b/tests/fixtures/sprites/expected/all_2.json new file mode 100644 index 000000000..b95ec54b2 --- /dev/null +++ b/tests/fixtures/sprites/expected/all_2.json @@ -0,0 +1,30 @@ +{ + "another_bicycle": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 40, + "y": 32 + }, + "bear": { + "height": 32, + "pixelRatio": 2, + "width": 32, + "x": 40, + "y": 0 + }, + "bicycle": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 70, + "y": 32 + }, + "sub/circle": { + "height": 40, + "pixelRatio": 2, + "width": 40, + "x": 0, + "y": 0 + } +} diff --git a/tests/fixtures/sprites/expected/all_2.png b/tests/fixtures/sprites/expected/all_2.png new file mode 100644 index 0000000000000000000000000000000000000000..48d9c7f8b1efcc599f50c9590f0bbeb4dde1ff0a GIT binary patch literal 1546 zcmZWpdpy%?82`Cnvg48@!cNXI6DGOlp4&z^Y=^_j66-|buu@rAr<~(1Hktd7tjQE| zH<1##E8%3*DwJz0#l-o2KIEVCem?K>e4poizTfwK-anoM4>u=>w2CwU01%Y3y(j1c z!EGfe0@@GSZeIaFsNKUA;{b+>{j5SfauY-V&`AloVYic+m~Fre zN7a#hDHueSm-bd!bK)6_7o3WPgF0ZQDL!CZ=LcnnOTlbYs6V*1ak$!897qQ#TdW5p z@4D-!w(yoM3w7Em0QLVbwR4p3Z_JE1z7y^MMwm0@$8|f&r`eZBD>LbV7R3b1VuJMu zGcDiW1Z;f#K{m}7N&6i+`-JtDeTT;`8*eC`tS^|XD;TNH9jnS3VWbW-Qu>N-w9r$Z z<&qv#h#i^4)|7Lt$sr9lPS?cvRmb>NMtR+g4rPR+8DZ$svv!36W_c%#a&U$;UjyQ4 zZz>i6J|ou4G|BBC8KvoswguY^vr@+$*@w~JYi@AJ42iIXDcJs^V5F(61N#}Nsj9C6 z(Nlq_DJ#MDNdF|WOGZip{0hqC(Js*8iJop=fE>6qpin3hiL|h`wzjdcL809}JUqPp z@%XUt@bK8!*tod3w6wJJ^z_`k!os4W;xZPis;Y_2X1BGq_4oG=3=E7l+(IbWK|T5V?i2x5Zowt%_9?U(z*%X=9R>+wmoe@cYIq zWm%7nb&VBnq7BI9FM_khLyQnc7+rXygZsU&E*Cc0`H=+A_H@m2@CTy3`!Bir5n`=*>$k^U%fnBq zHDxtA8kt5MF>(i@U25r6#|x`frHPi!UaWTrbg2*CS#wD5;{{T}1a1`8BU!N<{ikju z^W;;+#i}>d3b$S60V%m9jrE>k0Zu7v-EVkv1v>F#-|0QdQ_;;9qB6`1<)EJZqoc_7 z9$6tRrVp>EB|*MVSc@(bqb;H=Wy(W8%ZiZHga9QKq6B~|>1Yltt78vH#Z{Mwu76j3 z_-|`U>#@0Eg?;C#>#osCpOnH?_h>byiTTMB@tKhzp}>+&gxN&CzWb$6kGCdKFzNYth2@Z<^pTE!%Ri z!qs@0iiU~jUxoFVq7S&ypDaz@-fMxTr(cznP#3hlelxEwXQu5hY4K%kZF-?hXLOmU z@9eWH@jjkB7`fjvq&JN<6ECZ(^Kt!5;<|rv5pPfP88wp>B99zI*NJ$Z+oDr}bNyTF zbwtMH-(DNk4=m##c1~iS6ooG4_^K&o))rNxhVXmO9yrPze@@t)zjz-Im(O&ZRqZ>j z-H$n6um4G%Pwxqz?F*U_LuS~!Hb$fFzT(cZrNfD1bGU(84IJ}Ty9F>GjNS|WNEBG$dihXsK@9Xr?`{nXCi&k!lhk28w4x% zFoSY}PGlx$5UjH4&8x>cQ{8R5V-vs`#PF6_Jm#TF}ydSayemuG)6ju93X>%Kk> zip(KyKTz`!UQ;1l8s-WK zo*tnwHB=kOo))SxIYa}yt>dEq8>tSs0`gu8L}kPc~^TXviQ!{EXIlL$zKWTmN|fvRgZs+}tqj^xByxXV;#bU2|kg$-dqkp!O|w(VOd{ zCRXRJ%=c=}Pnw@(IWx+zE#3v_wnk57pqViyl3rROCYnmxs*0+jTzov-yxg2XTNa8s zMFJ_Ok|4ie1_1#9c?AOldj|)9|A2t-_=JSa{DOk|`i6#v2@~eeU$9`o`VIT{A2@LO z{P_zPu3o)*{r>$24<0^${`UR*4;hfwuk4DrFQo8W2LjpAchxf6xc&EEd#>J6%B#3;<`Kp(8oA~-Pn@+~YP;Al z+&!yZQtnEz!jiKS9@-@cpG`Iw%ibKw*?Zt7L@5;(EUY+ULzuIaY_4Jh{X^H|MkB8>4Txa<}iUJ$7wDx39V3jV?=5 zww*a|+>g1mUSGaBCGL)YkQu8egUq?0M1?c41ujK5ax?^&D;+MbS^wbHLmTI3ro2pt zzQ-;JnCRi$w02I*(<-g@V9iNPfeGErtp*krF;jQC>v%A9B>6-yk#ks}CwW;eaEFrQ cXZgQOUKQ(hDVP}^14bo-r>mdKI;Vst0A|WPIRF3v literal 0 HcmV?d00001 diff --git a/tests/fixtures/sprites/expected/src1_2.json b/tests/fixtures/sprites/expected/src1_2.json new file mode 100644 index 000000000..7ac8efa9c --- /dev/null +++ b/tests/fixtures/sprites/expected/src1_2.json @@ -0,0 +1,23 @@ +{ + "another_bicycle": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 40, + "y": 32 + }, + "bear": { + "height": 32, + "pixelRatio": 2, + "width": 32, + "x": 40, + "y": 0 + }, + "sub/circle": { + "height": 40, + "pixelRatio": 2, + "width": 40, + "x": 0, + "y": 0 + } +} diff --git a/tests/fixtures/sprites/expected/src1_2.png b/tests/fixtures/sprites/expected/src1_2.png new file mode 100644 index 0000000000000000000000000000000000000000..81bd0a5e393c8219c04bb05cf634bbd2f4b6f4ef GIT binary patch literal 1490 zcmeAS@N?(olHy`uVBq!ia0vp^9zblz!3-q#d#qi_z`%GYz$e5N$bEl){rm)j|3DT4 z!_g_((<3w>?5UyJ5XSzVtZAW8!O0;|#_9sk?KM$TLbRp@YeJOm=}MmxtT8oI185FV zWOrK<)Et-=xZWutP%Tqodbc*jY^{$0Y6oh8=-N~hzP%+8!iJE`a@`>cfaYTw1(%u| zZ??L~7tVzFd-Gt%JY zvGq^(FMqs$*{z*R?(LX&Z~MF}n`htLFzw39zRN3n&n;@*yRh%%?3yD}N)Ash+1H!1 zuP1wFYx0)5=*@Le>&k;CR_CrP@LHMgwItJNev;*!IMZ1%#xtV~OOnHN`m}?83cepUO_>@z`(%9-rnB9!NJqZKOi6=JU$^Izo4L? zzP`Spp`pLOf5L=^xf*6d6TJ)@S6t1y(X>@+s?;j6Embbdul-(H_3!??8zFq0FIRcA{vc3quDf*&x_4)V2>9wCrtDbAzQgKT^%C~v3=Nkj( zv&UZb{t~!l6TE-pt&B@P(aV&|tminKHr(~f^ZM)R4TtCbD)ryxrN1yWAgxKO*+9?C zHg#rPP12JenQw1SZ12CgX}QV$*20Ju3r2ZUo2QfWGnaqgzw?nMZ`1o80l}q9Lru?5 z7b);<4fB1`A`rOrXX(wz<$TvtGHTTpE$)4t92XUx7v$dibnDXNU+ua%s*bno1$MlD zc}ZZ2i>u2Dl`nz&-4x^MIK2E^q}o5Jwa#dnBr@r&2eSy*sT~4k%Cd_Bm?vl*I;7K} zEb@w>!M$~=ih<{;XZQC9o_>4#iS?58XHFGpe=#Oi@vFzP*#B4O|9M}o zJ+H`h?#xT-YrZ(Xiv4kmdqH^hg0r05#TVGjnijUk{U|!*#J5lV{_VS!+4?<8?jFv! z`Q}*t{HDLA!dT%x@ zi*W2n$y@jK=P6Zd|Dcv*Jq5Lge`YHBK5zf+q^{}G@+RdcYsN}FXU%n16Mn5eqqocO zs3e2rGwv7%bEB-y^ONiBj=p)Y&mvj%*&~VK|17IFa@EY+X(G0J+R9CVPowN7teZ7e zdh_8JkHdvmWdr<9@_FlBYMn9rU5Mpnt|G7X{Apjy(yv(F`dq(c`%fnMFV{8a9uY}T zTz@3fbo#Bd>C3OIk6o}pI8*PS+~#vHbDyn>++B08M%+9^Z~2$!Z%f|<%)D%HDTZ&& z+tf-Xw~3n@x7sd0CYg~OQ{0qYt@rszT|)iDo4jk13$xc;y>q}yO+7kIFMaNTUyRn} ZjHjXkKUs9?IRLXYgQu&X%Q~loCID;Qo!0;W literal 0 HcmV?d00001 diff --git a/tests/fixtures/sprites/expected/src2_1.json b/tests/fixtures/sprites/expected/src2_1.json new file mode 100644 index 000000000..33c5a847b --- /dev/null +++ b/tests/fixtures/sprites/expected/src2_1.json @@ -0,0 +1,9 @@ +{ + "bicycle": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 0, + "y": 0 + } +} diff --git a/tests/fixtures/sprites/expected/src2_1.png b/tests/fixtures/sprites/expected/src2_1.png new file mode 100644 index 0000000000000000000000000000000000000000..490e533575d7ca1e9a1d80119835d0ec18ca91c8 GIT binary patch literal 265 zcmeAS@N?(olHy`uVBq!ia0vp^{2R(9&C|E}ZL-!()&ku6((b=948%XJp9e{)_Y&S&yf+&?Lb|Lg*`?Gv@% yTNr6c-0Cx2*__F^LMJ;XM|oOcA*bH|KOY$%PhttUwBSev$U~m4elF{r5}E)>6iu`M literal 0 HcmV?d00001 diff --git a/tests/fixtures/sprites/expected/src2_2.json b/tests/fixtures/sprites/expected/src2_2.json new file mode 100644 index 000000000..b7ec269c2 --- /dev/null +++ b/tests/fixtures/sprites/expected/src2_2.json @@ -0,0 +1,9 @@ +{ + "bicycle": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 0, + "y": 0 + } +} diff --git a/tests/fixtures/sprites/expected/src2_2.png b/tests/fixtures/sprites/expected/src2_2.png new file mode 100644 index 0000000000000000000000000000000000000000..c5269e3201467167b7d5b1d8efdd0e539dbccdab GIT binary patch literal 399 zcmeAS@N?(olHy`uVBq!ia0vp^av;pX3?zBp#Z3TG-T^)#u0Wbv;8}uI3D7Kwk|4ie z1_1>FdxwC8g8GL32@BS5*ni;sh5HZQfB60C(l4Nz+nz3tAr-gIP6*69tRTQDZ}DKp zwqtE9=l}XgH3o)Se%U>fKjGV3FYPaOISiil8-peNbF&UqX!Skvd*ID$-J^T-O10L5 z_UN;d(kg_$o;ssedtf>1;`kY=2e%#Aa!AKjZi3k5<6LtO8G5cO`K;UeBIOTXinb+# zzW8g+;2BqMZ#etD@dnq?7d6|K&bxE<&NjEVEUI%gTe~DWM4f@0pBy literal 0 HcmV?d00001 diff --git a/tests/fixtures/sprites/src1/another_bicycle.svg b/tests/fixtures/sprites/src1/another_bicycle.svg new file mode 100644 index 000000000..cfb8f9b3a --- /dev/null +++ b/tests/fixtures/sprites/src1/another_bicycle.svg @@ -0,0 +1,3 @@ + + + diff --git a/tests/fixtures/sprites/src1/bear.svg b/tests/fixtures/sprites/src1/bear.svg new file mode 100644 index 000000000..084a1f857 --- /dev/null +++ b/tests/fixtures/sprites/src1/bear.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/tests/fixtures/sprites/src1/sub/circle.svg b/tests/fixtures/sprites/src1/sub/circle.svg new file mode 100644 index 000000000..2f35d511a --- /dev/null +++ b/tests/fixtures/sprites/src1/sub/circle.svg @@ -0,0 +1,3 @@ + + + diff --git a/tests/fixtures/sprites/src2/bicycle.svg b/tests/fixtures/sprites/src2/bicycle.svg new file mode 100644 index 000000000..484d5745a --- /dev/null +++ b/tests/fixtures/sprites/src2/bicycle.svg @@ -0,0 +1,12 @@ + + + + diff --git a/tests/test.sh b/tests/test.sh index 31b77df8f..ce1ffcac6 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -251,6 +251,19 @@ test_pbf fnc_0_0_0 function_zxy_query/0/0/0 test_pbf fnc2_0_0_0 function_zxy_query_test/0/0/0?token=martin test_png pmt_0_0_0 pmt/0/0/0 +test_jsn spr_src1 sprite/src1.json +test_png spr_src1 sprite/src1.png +test_jsn spr_src1_2x sprite/src1@2x.json +test_png spr_src1_2x sprite/src1@2x.png +test_jsn spr_mysrc sprite/mysrc.json +test_png spr_mysrc sprite/mysrc.png +test_jsn spr_mysrc_2x sprite/mysrc@2x.json +test_png spr_mysrc_2x sprite/mysrc@2x.png +test_jsn spr_cmp sprite/src1,mysrc.json +test_png spr_cmp sprite/src1,mysrc.png +test_jsn spr_cmp_2x sprite/src1,mysrc@2x.json +test_png spr_cmp_2x sprite/src1,mysrc@2x.png + kill_process $PROCESS_ID validate_log test_log_2.txt